home *** CD-ROM | disk | FTP | other *** search
/ Skunkware 98 / Skunkware 98.iso / osr5 / sco / scripts / proctree < prev    next >
Encoding:
AWK Script  |  1997-08-26  |  80.6 KB  |  2,161 lines

  1. #!/usr/local/bin/gawk -f
  2. # @(#) proctree.gawk 1.4.1 97/07/19
  3. # 96/05/29 john h. dubois iii
  4. # 96/08/27 Make field list case insensitive
  5. # 96/12/01 Print tree in a nice format.  Changed -w to -C; added new -was.
  6. # 97/04/15 Let command names be given as args, not just with -n
  7. # 97/07/16 1.4 Added AOp options.  Made "both" be default; removed b option.
  8. # 97/07/19 1.4.1 Added header & HI options.
  9.  
  10. BEGIN {
  11.     SUBSEP = ","    # To make debugging printout clearer
  12.     Name = "proctree"
  13.     Usage = "Usage: " Name \
  14. " [-hHrsacpN] [-w<width>] [-i<indent>] [-C<columns>] [-t<tty,...>]\n"\
  15. "       [-n<pattern>] [-u<user,...>] [-P<ps-field,...>] [-I<max-equ-indent>]\n"\
  16. "       [-A<ancestor-distance>] [-O<offspring-distance>] [process-ID|name ...]"
  17.     rcFile = ".proctree"
  18.     ARGC = Opts(Name,Usage,"i>C<P:u:t:n:cpw>saA<O<I#Hhx>",0,
  19.     "~/" rcFile ":$HOME/" rcFile,
  20.     "INDENT,COLUMNS,FIELDS,USERS,TTYS,NAMES,CHILDREN,PARENTS,WIDTH,SPACES,"\
  21.     "ASCII,ANCESTORS,OFFSPRING,MAXEQUINDENT,NOHEADER",0,"N",0,"","I,w")
  22.     nIndent = 2        # Default indent is 2 spaces
  23.     
  24.     # Put UID first because unlike PID it is left-adjusted, so the indenting
  25.     # is consistent.
  26.     FieldList = "UID,PID,TTY,ARGS"
  27.     if ("h" in Options) {
  28.     printf \
  29. "%s: Print a process tree.\n"\
  30. "%s\n"\
  31. "%s prints a tree of processes executing on the system, arranged according\n"\
  32. "to parent-child relationships.  Each process is printed, followed by an\n"\
  33. "indented list of its children, then a further indented list of that\n"\
  34. "process' children, etc.  If any process IDs are given, only those\n"\
  35. "processes and their ancestors and children are displayed.  Any argument that is not a\n"\
  36. "non-negative integer is taken to be a process name and is merged with any\n"\
  37. "pattern given with -n.  If both names and PIDs are given, a process must\n"\
  38. "satisfy both types of selectors in order to be printed.\n"\
  39. "Options:\n"\
  40. "Some of the following options can also be set by assigning values to\n"\
  41. "variables in a configuration file named %s, which is searched for in the\n"\
  42. "invoking user's home directory and in the directory specified by the\n"\
  43. "environment variable UHOME, if it is set (if both files exist, values set\n"\
  44. "in the former take precedence).  Variables are assigned to with the\n"\
  45. "syntax:  varname=value  or in the case of flags, by simply putting the\n"\
  46. "indicated variable name in the file without a value.  Variable names are\n"\
  47. "given in parentheses in the option descriptions.\n"\
  48. "-h: Print this help.\n"\
  49. "-P<ps-field,...>: For each process, print the given fields.  The possible\n"\
  50. "    fields are: UID (user ID), PPID (parent process ID), C (CPU scheduling\n"\
  51. "    value), STIME (start time), TTY (TTY nam), TIME (CPU time used), CMD\n"\
  52. "    (first element of arg vector), CMDT (like CMD, but only the final\n"\
  53. "    pathname component), ARGS (command + arguments), and COMM (name of\n"\
  54. "    command being executed).  The default is: %s\n"\
  55. "    (FIELDS)\n"\
  56. "-N: Do not read the configuration file.\n"\
  57. "The following options control the manner in which the tree is drawn:\n"\
  58. "-i<indent>: The number of character positions to indent when showing the\n"\
  59. "    children of a process.  The minimum is 1.  The default is %d.  (INDENT)\n"\
  60. "-I<max-equ-indent>: Attempt to equalize the total indentation of each line\n"\
  61. "    so that the ps columns are aligned. <max-equ-indent> is the maximum\n"\
  62. "    number of indent positions to equalize to.  If -1 is given, all lines\n"\
  63. "    will be indented to the same distance that the furthest indented line\n"\
  64. "    is.  Otherwise, if the deepest indent is less than or equal to the\n"\
  65. "    value given, the number of indents used will be the same as the\n"\
  66. "    deepest indent.  If the deepest indent is larger than the value given,\n"\
  67. "    those lines indented further will not line up.  (MAXEQUINDENT)\n"\
  68. "-H: Do not print a header listing the names of the ps fields (NOHEADER)\n"\
  69. "-C<columns>: The screen width to use.  Output is truncated to <columns>\n"\
  70. "    columns.  The default is to use one fewer than the width of the user's\n"\
  71. "    terminal. If -C0 is given, the output is not truncated.  (COLUMNS)\n"\
  72. "-w<width>: The tree is drawn in an alternate format, with one of the\n"\
  73. "    children of each process printed on the same line to save space\n"\
  74. "    (reduce the number of lines printed).  The data to be printed for any\n"\
  75. "    process that has children to be printed is truncated or padded to\n"\
  76. "    <width> characters.  <width> must be at least 2.  (WIDTH)\n"\
  77. "-a: Normally, the tree is drawn using box-drawing character appropriate to\n"\
  78. "    the type of terminal the program is invoked from.  If the terminal\n"\
  79. "    does not have box-drawing characters available or -a is given, the\n"\
  80. "    tree is drawn using ASCII characters.  (ASCII)\n"\
  81. "-s: Draw the tree using nothing but spaces for indentation.  (SPACES)\n"\
  82. "The following options restrict the processes that are displayed to the\n"\
  83. "processes they select and their ancestors and children:\n"\
  84. "-u<user,...>: Select processed owned by any of the users in the\n"\
  85. "    comma-separated list.  (USERS)\n"\
  86. "-t<tty,...>: Select processed whose controlling TTY is one of those in\n"\
  87. "    the comma-separated list.  (TTYS)\n"\
  88. "-n<pattern>: Select processes whose name (basename of argv[0]) matches the\n"\
  89. "    egrep(C)-style pattern <pattern>, which is implicitely anchored at the\n"\
  90. "    start and end.  If COMM is given with -P, the executable name (trailing\n"\
  91. "    pathname component of the file being executed) is used instead. (NAMES)\n"\
  92. "The following options limit which processes among the ancestors and\n"\
  93. "children of processes selected by process ID or the -u, -t, and -n options\n"\
  94. "should be displayed (by default, all children and ancestors are displayed):\n"\
  95. "-A<ancestor-distance>: Instead of displaying all ancestors of processes,\n"\
  96. "    display only those that are within <ancestor-distance> generations of a\n"\
  97. "    selected process.  Example: -A2 displays only selected processes, their\n"\
  98. "    parents, and their grandparents (and if -p has not been given, their\n"\
  99. "    children).  (ANCESTORS)\n"\
  100. "-O<offspring-distance>: Like -A, but controls how many generations of\n"\
  101. "    offspring are displayed.  (OFFSPRING)\n"\
  102. "-c: Equivalent to -A0; print only the selected processes and their\n"\
  103. "    children.  (CHILDREN)\n"\
  104. "-p: Equivalent to -O0; print only the selected processes and their\n"\
  105. "    parents.  (PARENTS)\n"\
  106. "-cp causes only explicitly selected processes to be displayed.\n",
  107.     Name,Usage,Name,rcFile,nIndent,FieldList
  108.     exit 0
  109.     }
  110. ## Option/argument processing
  111.     if ("x" in Options)
  112.     Debug = Options["x"]
  113.  
  114.     if ("A" in Options)    # Number of ancestors to print
  115.     Ancestors = Options["A"]
  116.     else
  117.     Ancestors = -1
  118.     if ("O" in Options)    # Number of generations of offspring to print
  119.     Offspring = Options["O"]
  120.     else
  121.     Offspring = -1
  122.     if ("c" in Options)
  123.     Ancestors = 0
  124.     if ("p" in Options)
  125.     Offspring = 0
  126.  
  127.     if ("P" in Options)
  128.     FieldList = toupper(Options["P"])
  129.     MakeSet(GetFields,FieldList,",")
  130.     GetFields["PPID"]    # always need this
  131.     delete GetFields["PID"]
  132.  
  133.     if (SelectUsers = ("u" in Options)) {
  134.     n = MakeSet(Users,Options["u"],",")
  135.     if (Debug)
  136.         printf "%d users in user list.\n",n > "/dev/stderr"
  137.     GetFields["UID"]
  138.     }
  139.     if (SelectTTYs = ("t" in Options)) {
  140.     MakeSet(TTYs0,Options["t"],",")
  141.     for (tty in TTYs0)
  142.         TTYs[canonTTY(tty)]
  143.     GetFields["TTY"]
  144.     }
  145.     if (SelectNames = ("n" in Options))
  146.     NamePat = Options["n"]
  147.  
  148.     if (ARGC > 1) {
  149.     if (Debug)
  150.         printf "Got %d PID(s)/name(s)\n",ARGC-1 > "/dev/stderr"
  151.     for (i = 1; i < ARGC; i++) {
  152.         arg = ARGV[i]
  153.         if (arg ~ /^[0-9]+$/) {
  154.         SelectPIDs = 1
  155.         givenPIDs[arg]
  156.         }
  157.         else if (SelectNames)
  158.         NamePat = NamePat "|" arg
  159.         else {
  160.         NamePat = arg
  161.         SelectNames = 1
  162.         }
  163.     }
  164.     }
  165.     if (SelectNames) {
  166.     NamePat = "^(" NamePat ")$"
  167.     # CMD is incompatible with COMM, so only get it if COMM not requested
  168.     cmdField = ("COMM" in GetFields) ? "COMM" : "CMD"
  169.     GetFields[cmdField]
  170.     }
  171.  
  172. ## Gather ps data
  173.     if ((e = getPS(PIDs,Procs,set2list(GetFields,","),Children,Debug > 5)) < 0)
  174.     {
  175.     print (e == -2) ? "Bad field name given." : "ps failed." > "/dev/stderr"
  176.     exit 1
  177.     }
  178.     delete PIDs["ps"]
  179. ## Process data
  180.     # Make PIDs[] be a process -> process-parent map,
  181.     # and deal with children with no parent found
  182.     for (pid in PIDs) {
  183.     PIDs[pid] = parent = Procs[pid,"PPID"]
  184.     if (Debug > 2)
  185.         printf "Parent of %d is %d\n",pid,parent > "/dev/stderr"
  186.     if (!(parent in PIDs)) {
  187.         # if parent is not in PIDs, assume it is due to ps snapshot failure
  188.         # and make the process be a child of init
  189.         PIDs[pid] = 1
  190.         Children[1] = Children[1] "," pid
  191.     }
  192.     }
  193.     # If not all processes are to be displayed, mark those that should be.
  194.     if (SelectUsers || SelectTTYs || SelectNames || SelectPIDs) {
  195.     if (Debug)
  196.         print "Marking selected processes..." > "/dev/stderr"
  197.     for (pid in PIDs)
  198.         # If a process is selected...
  199.         if ( (!SelectUsers || Procs[pid,"UID"] in Users) &&
  200.         (!SelectTTYs || canonTTY(Procs[pid,"TTY"]) in TTYs) &&
  201.         (!SelectNames || basename(Procs[pid,cmdField]) ~ NamePat) &&
  202.         (!SelectPIDs || pid in givenPIDs) ) {
  203.         SelectedPIDs[pid]
  204.         if (Debug > 1)
  205.             printf " +%d",pid > "/dev/stderr"
  206.         }
  207.     if (Ancestors)
  208.         markParents(SelectedPIDs,DisplayPPIDs,PIDs,Ancestors)
  209.     # Mark decendants of selected PIDs in DisplayCPIDs[]
  210.     if (Offspring)
  211.         for (pid in SelectedPIDs)
  212.         doChildren(pid,DisplayCPIDs,Children,Offspring)
  213.     # Children & parents have been marked in separate arrays, so that
  214.     # marking processes in one direction doesn't prevent marking in the
  215.     # other when there is a common intermediate process.  Now merge marked
  216.     # processes with explicitly selected processes.
  217.     Union(DisplayCPIDs,DisplayPPIDs,SelectedPIDs)
  218.     }
  219.     else {
  220.     # Mark all processes.
  221.     SelectedPIDs[1]
  222.     doChildren(1,SelectedPIDs,Children,-1)
  223.     }
  224.     if (Debug > 1)
  225.     print "" > "/dev/stderr"
  226.  
  227.     split(FieldList,Fields,",")
  228.     preOrderTraverse(1,Children,1,"",0)
  229.  
  230.     if ("C" in Options) {
  231.     Cols = Options["C"]
  232.     HeadTailInit(-1,Cols ? Cols : -1,0,0)
  233.     }
  234.     else
  235.     HeadTailInit(-1)
  236.     if ("i" in Options)
  237.     nIndent = Options["i"]
  238.     if ("I" in Options)
  239.     equIndent = Options["I"]
  240.     if (!("H" in Options))
  241.     treeData["HEADER"] = makePSline(-1,Procs,Fields)
  242.     if (Debug)
  243.     print "" > "/dev/stderr"
  244.     if ("a" in Options)
  245.     delete ENVIRON["TERM"]
  246.     DrawTrees(treeData,nIndent,("w" in Options) ? Options["w"] : 0,emptyArr,
  247.     "s" in Options,term,1,COLUMNS ? (COLUMNS-1) : 0,0,0,equIndent)
  248.     if (Debug)
  249.     print "" > "/dev/stderr"
  250. }
  251.  
  252. # Globals: Debug
  253. function markParents(SelectedPIDs,DisplayPPIDs,Parents,Ancestors,
  254. pid,numAnc,p,p2) {
  255.     for (pid in SelectedPIDs) {
  256.     # Mark every proc up the chain as good by storing its pid
  257.     # in DisplayPPIDs[], until we hit init or an already-marked
  258.     # proc.
  259.     numAnc = 0
  260.     for (p = pid; p >= 1; p = Parents[p]) {
  261.         # If only marking ancestors for a limited distance up
  262.         # chain, must continue up chain even if this process
  263.         # is marked already because it may be marked due to
  264.         # one of its decendants, in which case the chain won't
  265.         # already be marked far enough.
  266.         if ((p in DisplayPPIDs) && (Ancestors == -1) ||
  267.         (Ancestors != -1) && (numAnc > Ancestors))
  268.         break
  269.         numAnc++
  270.         if (Debug > 1 && numAnc > Ancestors)
  271.         printf "\nHit ancestor limit\n" > "/dev/stderr"
  272.         DisplayPPIDs[p]
  273.         if (!(p in Parents)) {
  274.         printf "No parent for process %s?\n",
  275.         p > "/dev/stderr"
  276.         for (p2 = pid; p2 != p; p2 = Parents[p2])
  277.             printf "%d <- ",p2 > "/dev/stderr"
  278.         printf p > "/dev/stderr"
  279.         break
  280.         }
  281.     }
  282.     }
  283. }
  284.  
  285. # Mark decendants of pid in DisplayCPIDs[]
  286. # Globals: DisplayCPIDs[], Children[]
  287. function doChildren(pid,DisplayCPIDs,Children,numChildren,  Elem,child) {
  288.     if (pid in Children) {    # If this process has children, mark them
  289.     if (numChildren != -1)
  290.         numChildren -= 1
  291.     MakeSet(Elem,Children[pid],",")
  292.     for (child in Elem)
  293.         # If not marked yet...
  294.         if (!(child in DisplayCPIDs)) {
  295.         DisplayCPIDs[child]
  296.         if (numChildren) {
  297.             if (Debug)
  298.             printf " c%d",child > "/dev/stderr"
  299.             doChildren(child,DisplayCPIDs,Children,numChildren)
  300.         }
  301.         }
  302.     }
  303. }
  304.  
  305. # preOrderVisit: Build a tree data structure for use by DrawTrees().
  306. # We will be visited in pre-order.
  307. # Our mission: to identify marked subtrees within the process tree as a whole,
  308. # and make a DrawTrees() style tree out of each one.
  309. # This is done by completely processing each tree when its topmost node is
  310. # found, and marking each processed node so that it will not be further
  311. # processed when preOrderTraverse() calls us with the tree's subnodes.
  312. # Input variables:
  313. # Node:   PID to be checked for whether it is the top of a marked tree OR
  314. #         PID to be checked for whether it is part of a marked tree (depending
  315. #         on the value of Data).
  316. # Index:  Index to store this process' data under in treeData[].
  317. # Data is true if we are processing marked tree subnodes, false if we are
  318. # searching for the top of a marked tree.
  319. # Globals:
  320. # ProcessedNodes[]: Maintained as the set of processed nodes.
  321. # TopIndex is maintained as the number of the current tree being built.
  322. # Procs[], Fields[]: For use in building a ps output line.
  323. # treeData[]: DrawTrees()-style data structure to build.
  324. # Children[]: Children of each process.
  325. # Debug: If Debug is > 1, debugging info will be printed.
  326. # Return value:
  327. # True if the children of this node should be processed.
  328. function preOrderVisit(NodeID,Node,Ind,Data,  ret,psLine) {
  329.     if (Data) {    # Processing a marked node's subnodes
  330.     ProcessedNodes[Node]
  331.     if (ret = (Node in SelectedPIDs)) {
  332.         if (Debug > 1) 
  333.         printf "Visiting marked node %d; index: %s\n",
  334.         NodeID,Ind > "/dev/stderr"
  335.         psLine = makePSline(Node,Procs,Fields)
  336.         gsub("^[ \t]+|[ \t]+$","",psLine)    # discard leading/trailing ws
  337.         treeData[Ind] = psLine
  338.     }
  339.     }
  340.     else {
  341.     if ((Node in SelectedPIDs) && !(Node in ProcessedNodes)) {
  342.         # Start a new tree
  343.         if (Debug > 1) 
  344.         printf "Starting new tree rooted at PID %d\n",
  345.         Node > "/dev/stderr"
  346.         preOrderTraverse(Node,Children,1,++TopIndex,1)
  347.     }
  348.     ret = 1
  349.     }
  350.     return ret
  351. }
  352.  
  353. ### Begin ps lib
  354. # getPS 1.1    
  355. # 96/10/09    john h. dubois iii (john@armory.com)
  356. # 96/02/11    Added Debug flag.
  357. # 96/05/09    Added COMM field.
  358. # 96/05/23    Added selection args, and saving of "ps" PID.
  359. # 96/05/25    Added makePSline()
  360. # 96/10/09    Added RUSER field.
  361. # 96/12/14    Added CMDT field.
  362.  
  363. # Note: makePSline() needs assign() from array lib.
  364. # to do: generalize based on -o args to 5.0 ps
  365.  
  366. # Do a ps -f and save the output into an array, indexed by pid and field name.
  367. # Input vars:
  368. # Fields: Comma-separated list of fields to put in Procs.
  369. # If Debug is true, debugging info is output.
  370. # selectionArgs may be set to ps options that will report on selected processes
  371. # (e.g. -usomeone -ttty01)
  372. # The default for selectionArgs is -e, which causes information on all
  373. # processes to be recorded.
  374. #
  375. # Output vars:
  376. # PIDs[]: the set of all PIDs seen.
  377. # Also, the element with index "ps" is set to the PID for the ps process.
  378. # Procs[pid,fieldname]: output by field.
  379. #
  380. # Possible fields are:
  381. # UID: User ID; name if available, else number.
  382. # RUSER: Real user ID; name if available, else number.  Only available under
  383. #       5.0, and cannot be requested along with UID.
  384. # PPID: Parent process ID.
  385. # C: CPU scheduling.
  386. # STIME: Start time.  If the start time in the ps output contains a space,
  387. # it is replaced with a "-".  "-" is returned for a defunct process.
  388. # TTY: tty name; may or may not have leading "tty" part.  "-" for defunct proc;
  389. # "?" for proc with no controlling tty.
  390. # TIME: CPU time used.
  391. # CMD: First element of arg vector.
  392. # CMDT: Like CMD, but just the tail (leading path components removed), unless
  393. #       the path ends with /, in which case it is identical to CMD.
  394. # ARGS: Entire (truncated) arg vector (command + args).
  395. # LINE: Entire ps output line.
  396. # COMM: Process accounting name of process: the name of the executable file,
  397. #       without path.  This is only available under 5.0, and cannot be
  398. #       requested along with CMD/CMDT or ARGS.
  399. #
  400. # The header line read is also put in Procs with the index "Header".
  401. # The PIDs of the children of each process are put in a comma-separated list
  402. # in Children[pid].
  403. # Return value: the number of processes found, or -2 if an invalid field name
  404. # is passed, or -1 if an error occurs reading from ps.
  405. # Globals: FS is set to " "
  406. #
  407. # ps -f produces output in these forms, under various conditions & releases:
  408. #     UID   PID  PPID  C    STIME     TTY        TIME CMD
  409. #    root 10118 10107  2   Jan-03   ttyp0    00:00:05 -ksh
  410. #    root 10118 10107  2   Jan 03   ttyp0    00:00:05 -ksh
  411. #    root 18197     1  0 08:02:56   ttyp0    00:00:03 /usr/bin/X11/scoterm -geo
  412. function getPS(PIDs,Procs,Fields,Children,Debug,selectionArgs,
  413. stimeI,pidI,ttyI,ppidI,WantLine,psArgs,psSet,newPS,
  414. FieldNames,Wanted,Cmd,getI,Field2Ind,i,Name,Lines,WantArgs,Header,CmdIndex,fn,
  415. wantCmdt) {
  416.     FS = " "    # magic pattern to reset FS to its default special behaviour
  417.     split("UID,PID,PPID,C,STIME,TTY,TIME,CMD",FieldNames,",")
  418.     # psSet[] maps field number to field name
  419.     split("user,pid,ppid,c,stime,tty,time,args",psSet,",")
  420.     # Alt[] maps new ps field names to field numbers
  421.     Alt["RUSER"] = 1
  422.     Alt["COMM"] = 8
  423.     FieldNames[0] = "LINE"
  424.     for (i in FieldNames)    # Field2Ind[] maps field names to field numbers
  425.     Field2Ind[FieldNames[i]] = i
  426.     split(Fields,Wanted,",")
  427.     pidI = Field2Ind["PID"]
  428.     ppidI = Field2Ind["PPID"]
  429.     stimeI = Field2Ind["STIME"]
  430.     ttyI = Field2Ind["TTY"]
  431.     timeI = Field2Ind["TIME"]
  432.     cmdI = Field2Ind["CMD"]
  433.     psArgs = "-f"
  434.     for (i in Wanted) {
  435.     Name = Wanted[i]
  436.     if (Debug)
  437.         printf "Asked for %s\n",Name > "/dev/stderr"
  438.     # getI[] is made to contain the indices of fields to record
  439.     if (Name == "ARGS")
  440.         WantArgs = 1
  441.     else if (Name == "LINE")
  442.         WantLine = 1
  443.     else if (Name == "CMDT")
  444.         wantCmdt = 1
  445.     else if (Name in Alt) {        # New ps fields
  446.         newPS = 1
  447.         # Change the name of this field to that of the alternate requested
  448.         psSet[Alt[Name]] = tolower(Name)
  449.         fn = Field2Ind[Name] = Alt[Name] # Map this name to its field number
  450.         getI[fn]
  451.         FieldNames[fn] = Name
  452.     }
  453.     else if (Name in Field2Ind)
  454.         getI[Field2Ind[Name]]
  455.     else
  456.         return -2
  457.     }
  458.     if (newPS) {
  459.     psArgs = ""
  460.     for (i = 1; i in psSet; i++)
  461.     psArgs = psArgs " -o" psSet[i]
  462.     }
  463.     Lines = 0
  464.     if (selectionArgs == "")
  465.     selectionArgs = "-e"
  466.     Cmd = "echo $$; exec /bin/ps " selectionArgs " " psArgs " < /dev/null"
  467.     if ((Cmd | getline PIDs["ps"]) != 1)
  468.     return -1
  469.     if ((Cmd | getline Header) != 1)
  470.     return -1
  471.     Procs["Header"] = Header
  472.     if (!(CmdIndex = index(Header,"CMD")) &&
  473.     !(CmdIndex = index(Header,"COMMAND")))
  474.     return -1
  475.     while ((Cmd | getline) == 1) {
  476.     PIDs[pid = $pidI]
  477.     if (Debug)
  478.         printf "Process %d (%d fields): %s\n",pid,NF,$0 > "/dev/stderr"
  479.     ppid = $ppidI
  480.     if (ppid in Children)
  481.         Children[ppid] = Children[ppid] "," pid
  482.     else
  483.         Children[ppid] = pid
  484.     if (WantArgs)
  485.         Procs[pid,"ARGS"] = substr($0,CmdIndex)
  486.     # Handle this as a special case so that it can be set before the
  487.     # line is (possibly) modified
  488.     if (WantLine)
  489.         Procs[pid,"LINE"] = $0
  490.     # Time field with either contain a : (time), a - (new date format),
  491.     # or neither, in which case it occupies 2 fields (old date format).
  492.     if (NF == 6) {    # old ps defunct proc
  493.         # Assign new values to fields, from right to left to avoid
  494.         # overwriting fields before value is moved
  495.         $cmdI = $ttyI
  496.         $timeI = $stimeI
  497.         $ttyI = "-"
  498.         $stimeI = "-"
  499.     }
  500.     if ($stimeI !~ "[-:]") {
  501.         if (!timePos)
  502.         timePos = index($0,$stimeI)
  503.         # Replace space in stime field with "-"
  504.         $0 = substr($0,1,timePos+2) "-" substr($0,timePos+5)
  505.     }
  506.     if (wantCmdt) {
  507.         Procs[pid,"CMDT"] = $cmdI
  508.         if ($cmdI !~ "/$")
  509.         sub(".*/","",Procs[pid,"CMDT"])
  510.     }
  511.     for (i in getI) {
  512.         Procs[pid,FieldNames[i]] = $i
  513.         if (Debug)
  514.         printf "%s=%s ",FieldNames[i],$i > "/dev/stderr"
  515.     }
  516.     if (Debug)
  517.         print "" > "/dev/stderr"
  518.     Lines++
  519.     }
  520.     close(Cmd)
  521.     return Lines
  522. }
  523.  
  524. # makePSline: generate a line containing desired fields from ps data.
  525. # pid is the ID of the process to generate a line for.
  526. # If a pid of -1 is passed, a header line is returned.
  527. # Procs[] is the ps data, as generated by getPS().
  528. # Fields[] is the set of fields desired in the output, with indexes starting
  529. #    at 1.  The values are field names as e.g. passed to getPS().
  530. # Sep is the separator to put between fields.  If null, a single space is used.
  531. # Return value: a line consisting of the fields requested, in the order of
  532. # their indices in Fields[].
  533. # Example:
  534. # split("UID,PID,PPID,C,STIME,TTY,TIME,CMD",FieldNames,",")
  535. # makePSline(pid,psOut,FieldNames)
  536. function makePSline(pid,Procs,Fields,Sep,  i,fieldName,line,width,value) {
  537.     if (Sep == "")
  538.     Sep = " "
  539.     if (!("PID" in _makePSlineWidths))
  540.     # Make TIME before right-adjusted; some versions of ps drop leading
  541.     # 0 fields from it.
  542.     Assign(_makePSlineWidths,
  543.     "UID=-8 PID=5 PPID=5 C=1 STIME=-8 TTY=-4 TIME=8 COMM=-8"," ","=")
  544.     for (i = 1; i in Fields; i++) {
  545.     fieldName = Fields[i]
  546.     if (fieldName in _makePSlineWidths)
  547.         width = _makePSlineWidths[fieldName]
  548.     else
  549.         width = ""
  550.     if (pid == -1)
  551.         value = fieldName
  552.     else if (fieldName == "PID")
  553.         value = pid
  554.     else
  555.         value = Procs[pid,fieldName]
  556.     if (fieldName == "TTY")
  557.         sub("^tty","",value)
  558.     line = line Sep sprintf("%" width "s",value)
  559.     }
  560.     return substr(line,length(Sep)+1)
  561. }
  562.  
  563. ### End ps lib
  564. ### Begin array routines
  565.  
  566. # InitArr: Initialize an array with values.
  567. # Ind and Vals are separated into lists on Sep.
  568. # For each item in Ind, an index with that name is created in Arr[],
  569. # and the value with the same position in Vals is stored in it.
  570. # Global variables: none.
  571. function InitArr(Arr,Ind,Vals,sep,  numind,indnames,values) {
  572.     split(Ind,indnames,sep)
  573.     split(Vals,values,sep)
  574.     for (numind in indnames)
  575.     Arr[indnames[numind]] = values[numind]
  576. }
  577.  
  578. function ClearArr(Arr,  Elem) {
  579.     for (Elem in Arr)
  580.     delete Arr[Elem]
  581. }
  582. # Subtract the values in Subtrahend from those in Minuend
  583. function SubtractArr(Minuend,Subtrahend,  Elem) {
  584.     for (Elem in Subtrahend)
  585.     Minuend[Elem] -= Subtrahend[Elem]
  586. }
  587. # For each element of the array In, an element is created in Out having
  588. # an index equal to the value of the element in In and a value equal to 
  589. # the index of the element in In.
  590. function Invert(In,Out,  Index) {
  591.     for (Index in In)
  592.     Out[In[Index]] = Index
  593. }
  594.  
  595. # Assign: make an array from a list of assignments.
  596. # An index with the name of each variable in the list is created in the array.
  597. # Its value is set to the value given for it.
  598. # Input variables: 
  599. # Elements is a string containing the list of variable-value pairs.
  600. # Sep is the string that separates the pairs in the list.
  601. # AssignOp is the string that separates variables from values.
  602. # Output variables:
  603. # Arr is the array.
  604. # Return value: the number of elements added to the set.
  605. # Example:
  606. # Assign(Arr,"foo=blot bar=blat baz=blit"," ","=")
  607. function Assign(Arr,Elements,Sep,AssignOp,
  608. Num,Names,Elem,Assignments,Assignment,i) {
  609.     Num = split(Elements,Assignments,Sep)
  610.     for (i = 1; i <= Num; i++) {
  611.     Assignment = Assignments[i]
  612.     Ind = index(Assignment,AssignOp)
  613.     Arr[substr(Assignment,1,Ind - 1)] = substr(Assignment,Ind + 1)
  614.     }
  615.     return Num
  616. }
  617.  
  618. ### End array routines
  619. ### Begin head-tail routines
  620.  
  621. # @(#) HeadTail.awk 96/05/09
  622. # 95/04/28 Added tail routines.
  623. # 96/05/09 Added all args to HeadTailInit()
  624.  
  625. # Turn on screen-bounded printing.
  626. # Current implementation sets global vars LINES, COLUMNS, LINEGAP, and COLGAP.
  627. # Sets the number of screen lines and rows to Lines and Rows.
  628. # If -1 is passed for either, turns off bounding in that dimension.
  629. # If either is not set or 0 is passed for it, its value is taken from the
  630. # environment, or if not set there, from terminfo, or if not set there, from
  631. # the defaults (24 and 80).
  632. # By default, the other functions in this library leave a "grace space" of
  633. # 1 column and 1 line.  If LineGap or ColGap is passed and is a non-negative
  634. # value, the line gap is set to it.
  635. function HeadTailInit(Lines,Cols,LineGap,ColGap,  Cmd) {
  636.     # tput will use values in environment, but we want to avoid running
  637.     # it if possible.
  638.     if (Cols > 0)
  639.     COLUMNS = Cols
  640.     else if (!Cols)
  641.     if ("COLUMNS" in ENVIRON)
  642.         COLUMNS = ENVIRON["COLUMNS"]
  643.     else {
  644.         Cmd = "exec tput cols"
  645.         Cmd | getline COLUMNS
  646.         close(Cmd)
  647.         if (COLUMNS == "")
  648.         COLUMNS = 80
  649.     }
  650.     if (Lines > 0)
  651.     LINES = Lines
  652.     else if (!Lines)
  653.     if ("LINES" in ENVIRON)
  654.         LINES = ENVIRON["LINES"]
  655.     else {
  656.         Cmd = "exec tput lines"
  657.         Cmd | getline LINES
  658.         close(Cmd)
  659.         if (LINES == "")
  660.         LINES = 24
  661.     }
  662.     LINEGAP = (LineGap != "" && LineGap >= 0) ? LineGap : 1
  663.     COLGAP = (ColGap != "" && ColGap >= 0) ? ColGap : 1
  664. }
  665.  
  666. # Do screen-bound printing.  
  667. # If LINES  is >0, the last LINES-LINEGAP lines are kept in a circular buffer.  
  668. # When TailFlush() is called, they are printed.
  669. # If LINES = 0, all lines are printed immediately.
  670. # If COLUMNS is >0, truncates Line to COLUMNS-COLGAP characters before printing
  671. # it.
  672. # Global vars: uses LINES & COLUMNS; sets/uses TailPtr;
  673. # saves lines in TailLines[] from 1..LINES-LINEGAP
  674. # Embedded newlines split the line into multiple lines; trailing newlines are
  675. # stripped.  Tabs are expanded to spaces.
  676. function TailPrint(Line) {
  677.     if (!LINES)
  678.     print Line
  679.     else {
  680.     if (++TailPtr > (LINES-LINEGAP))
  681.         TailPtr = 1
  682.     TailLines[TailPtr] = Line
  683.     }
  684. }
  685.  
  686. function TailFlush(  NumPrinted,Lines,Line,i,Buffer,PrintLines) {
  687.     if (!LINES)
  688.     return
  689.     NumPrinted = 0
  690.     PrintLines = LINES-LINEGAP
  691.     # Since lines may contain multiple lines, we must create a buffer to be
  692.     # printed by reading line buffer backwards.
  693.     # Stop when we have copied enough lines, or if we wrap around to the end
  694.     # and find that the entire line buffer was not used.
  695.     while (NumPrinted < PrintLines && TailPtr in TailLines) {
  696.     # Split line into individual lines, then process them last to first
  697.     Num = split(TailLines[TailPtr],Lines,"\n")
  698.     for (i = Num; i >= 1; i--) {
  699.         Line = Lines[i]
  700.         if (i == Num && Line == "")    # discard trailing newline
  701.         continue
  702.         # Put this line at the front of the print buffer
  703.         if (COLUMNS)
  704.         Buffer = substr(TabEx(Line),1,COLUMNS - COLGAP) "\n" Buffer
  705.         else
  706.         Buffer = Line "\n" Buffer
  707.         if (++NumPrinted == PrintLines)
  708.         break
  709.     }
  710.     if (!--TailPtr)    # Wrap pointer if neccessary
  711.         TailPtr = PrintLines
  712.     }
  713.     printf "%s",Buffer
  714. }
  715.  
  716. # Do screen-bound printing.  
  717. # If LINES >0, returns 0 when LINES-LINEGAP lines have been printed by
  718. # HeadPrint().  Otherwise returns 1.
  719. # If COLUMNS is >0, truncates Line to COLUMNS-COLGAP characters before printing
  720. # it.
  721. # Global vars: uses LINES, COLUMNS, LINEGAP, COLGAP; sets/uses LinesPrinted.
  722. # Line should not include newlines.
  723. function HeadPrint(Line) {
  724.     # Check first, in case some calls of this function to not check return
  725.     # value, and in case LINES is 1.
  726.     if (LINES && LinesPrinted >= (LINES-LINEGAP))
  727.     return 0
  728.     if (COLUMNS)
  729.     print substr(Line,1,COLUMNS - COLGAP)
  730.     else
  731.     print Line
  732.     if (LINES && ++LinesPrinted >= (LINES-LINEGAP))
  733.     return 0
  734.     return 1
  735. }
  736.  
  737. function ColPrint(Line) {
  738.     if (COLUMNS)
  739.     print substr(Line,1,COLUMNS - COLGAP)
  740.     else
  741.     print Line
  742.     return 1
  743. }
  744.  
  745. ### End head-tail routines
  746. ### Start of ProcArgs library
  747. # @(#) ProcArgs 1.11 96/12/08
  748. # 92/02/29 john h. dubois iii (john@armory.com)
  749. # 93/07/18 Added "#" arg type
  750. # 93/09/26 Do not count -h against MinArgs
  751. # 94/01/01 Stop scanning at first non-option arg.  Added ">" option type.
  752. #          Removed meaning of "+" or "-" by itself.
  753. # 94/03/08 Added & option and *()< option types.
  754. # 94/04/02 Added NoRCopt to Opts()
  755. # 94/06/11 Mark numeric variables as such.
  756. # 94/07/08 Opts(): Do not require any args if h option is given.
  757. # 95/01/22 Record options given more than once.  Record option num in argv.
  758. # 95/06/08 Added ExclusiveOptions().
  759. # 96/01/20 Let rcfiles be a colon-separated list of filenames.
  760. #          Expand $VARNAME at the start of its filenames.
  761. #          Let varname=0 and -option- turn off an option.
  762. # 96/05/05 Changed meaning of 7th arg to Opts; now can specify exactly how many
  763. #          of the vars should be searched for in the environment.
  764. #          Check for duplicate rcfiles.
  765. # 96/05/13 Return more specific error values.  Note: ProcArgs() and InitOpts()
  766. #          now return various negatives values on error, not just -1, and
  767. #          Opts() may set Err to various positive values, not just 1.
  768. #          Added AllowUnrecOpt.
  769. # 96/05/23 Check type given for & option
  770. # 96/06/15 Re-port to awk
  771. # 96/10/01 Moved file-reading code into ReadConfFile(), so that it can be
  772. #          used by other functions.
  773. # 96/10/15 Added OptChars
  774. # 96/11/01 Added exOpts arg to Opts()
  775. # 96/11/16 Added ; type
  776. # 96/12/08 Added Opt2Set() & Opt2Sets()
  777. # 96/12/27 Added CmdLineOpt()
  778.  
  779. # optlist is a string which contains all of the possible command line options.
  780. # A character followed by certain characters indicates that the option takes
  781. # an argument, with type as follows:
  782. # :    String argument
  783. # ;    Non-empty string argument
  784. # *    Floating point argument
  785. # (    Non-negative floating point argument
  786. # )    Positive floating point argument
  787. # #    Integer argument
  788. # <    Non-negative integer argument
  789. # >    Positive integer argument
  790. # The only difference the type of argument makes is in the runtime argument
  791. # error checking that is done.
  792.  
  793. # The & option is a special case used to get numeric options without the
  794. # user having to give an option character.  It is shorthand for [-+.0-9].
  795. # If & is included in optlist and an option string that begins with one of
  796. # these characters is seen, the value given to "&" will include the first
  797. # char of the option.  & must be followed by a type character other than ":"
  798. # or ";".
  799. # Note that if e.g. &> is given, an option of -.5 will produce an error.
  800.  
  801. # Strings in argv[] which begin with "-" or "+" are taken to be
  802. # strings of options, except that a string which consists solely of "-"
  803. # or "+" is taken to be a non-option string; like other non-option strings,
  804. # it stops the scanning of argv and is left in argv[].
  805. # An argument of "--" or "++" also stops the scanning of argv[] but is removed.
  806. # If an option takes an argument, the argument may either immediately
  807. # follow it or be given separately.
  808. # "-" and "+" options are treated the same.  "+" is allowed because most awks
  809. # take any -options to be arguments to themselves.  gawk 2.15 was enhanced to
  810. # stop scanning when it encounters an unrecognized option, though until 2.15.5
  811. # this feature had a flaw that caused problems in some cases.  See the OptChars
  812. # parameter to explicitly set the option-specifier characters.
  813.  
  814. # If an option that does not take an argument is given,
  815. # an index with its name is created in Options and its value is set to the
  816. # number of times it occurs in argv[].
  817.  
  818. # If an option that does take an argument is given, an index with its name is
  819. # created in Options and its value is set to the value of the argument given
  820. # for it, and Options[option-name,"count"] is (initially) set to the 1.
  821. # If an option that takes an argument is given more than once,
  822. # Options[option-name,"count"] is incremented, and the value is assigned to
  823. # the index (option-name,instance) where instance is 2 for the second occurance
  824. # of the option, etc.
  825. # In other words, the first time an option with a value is encountered, the
  826. # value is assigned to an index consisting only of its name; for any further
  827. # occurances of the option, the value index has an extra (count) dimension.
  828.  
  829. # The sequence number for each option found in argv[] is stored in
  830. # Options[option-name,"num",instance], where instance is 1 for the first
  831. # occurance of the option, etc.  The sequence number starts at 1 and is
  832. # incremented for each option, both those that have a value and those that
  833. # do not.  Options set from a config file have a value of 0 assigned to this.
  834.  
  835. # Options and their arguments are deleted from argv.
  836. # Note that this means that there may be gaps left in the indices of argv[].
  837. # If compress is nonzero, argv[] is packed by moving its elements so that
  838. # they have contiguous integer indices starting with 0.
  839. # Option processing will stop with the first unrecognized option, just as
  840. # though -- was given except that unlike -- the unrecognized option will not be
  841. # removed from ARGV[].  Normally, an error value is returned in this case.
  842. # If AllowUnrecOpt is true, it is not an error for an unrecognized option to
  843. # be found, so the number of remaining arguments is returned instead.
  844. # If OptChars is not a null string, it is the set of characters that indicate
  845. # that an argument is an option string if the string begins with one of the
  846. # characters.  A string consisting solely of two of the same option-indicator
  847. # characters stops the scanning of argv[].  The default is "-+".
  848. # argv[0] is not examined.
  849. # The number of arguments left in argc is returned.
  850. # If an error occurs, the global string OptErr is set to an error message
  851. # and a negative value is returned.
  852. # Current error values:
  853. # -1: option that required an argument did not get it.
  854. # -2: argument of incorrect type supplied for an option.
  855. # -3: unrecognized (invalid) option.
  856. function ProcArgs(argc,argv,OptList,Options,compress,AllowUnrecOpt,OptChars,
  857. ArgNum,ArgsLeft,Arg,ArgLen,ArgInd,Option,Pos,NumOpt,Value,HadValue,specGiven,
  858. NeedNextOpt,GotValue,OptionNum,Escape,dest,src,count,c,OptTerm,OptCharSet)
  859. {
  860. # ArgNum is the index of the argument being processed.
  861. # ArgsLeft is the number of arguments left in argv.
  862. # Arg is the argument being processed.
  863. # ArgLen is the length of the argument being processed.
  864. # ArgInd is the position of the character in Arg being processed.
  865. # Option is the character in Arg being processed.
  866. # Pos is the position in OptList of the option being processed.
  867. # NumOpt is true if a numeric option may be given.
  868.     ArgsLeft = argc
  869.     NumOpt = index(OptList,"&")
  870.     OptionNum = 0
  871.     if (OptChars == "")
  872.     OptChars = "-+"
  873.     while (OptChars != "") {
  874.     c = substr(OptChars,1,1)
  875.     OptChars = substr(OptChars,2)
  876.     OptCharSet[c]
  877.     OptTerm[c c]
  878.     }
  879.     for (ArgNum = 1; ArgNum < argc; ArgNum++) {
  880.     Arg = argv[ArgNum]
  881.     if (length(Arg) < 2 || !((specGiven = substr(Arg,1,1)) in OptCharSet))
  882.         break    # Not an option; quit
  883.     if (Arg in OptTerm) {
  884.         delete argv[ArgNum]
  885.         ArgsLeft--
  886.         break
  887.     }
  888.     ArgLen = length(Arg)
  889.     for (ArgInd = 2; ArgInd <= ArgLen; ArgInd++) {
  890.         Option = substr(Arg,ArgInd,1)
  891.         if (NumOpt && Option ~ /[-+.0-9]/) {
  892.         # If this option is a numeric option, make its flag be & and
  893.         # its option string flag position be the position of & in
  894.         # the option string.
  895.         Option = "&"
  896.         Pos = NumOpt
  897.         # Prefix Arg with a char so that ArgInd will point to the
  898.         # first char of the numeric option.
  899.         Arg = "&" Arg
  900.         ArgLen++
  901.         }
  902.         # Find position of flag in option string, to get its type (if any).
  903.         # Disallow & as literal flag.
  904.         else if (!(Pos = index(OptList,Option)) || Option == "&") {
  905.         if (AllowUnrecOpt) {
  906.             Escape = 1
  907.             break
  908.         }
  909.         else {
  910.             OptErr = "Invalid option: " specGiven Option
  911.             return -3
  912.         }
  913.         }
  914.  
  915.         # Find what the value of the option will be if it takes one.
  916.         # NeedNextOpt is true if the option specifier is the last char of
  917.         # this arg, which means that if the option requires a value it is
  918.         # the next arg.
  919.         if (NeedNextOpt = (ArgInd >= ArgLen)) { # Value is the next arg
  920.         if (GotValue = ArgNum + 1 < argc)
  921.             Value = argv[ArgNum+1]
  922.         }
  923.         else {    # Value is included with option
  924.         Value = substr(Arg,ArgInd + 1)
  925.         GotValue = 1
  926.         }
  927.  
  928.         if (HadValue = AssignVal(Option,Value,Options,
  929.         substr(OptList,Pos + 1,1),GotValue,"",++OptionNum,!NeedNextOpt,
  930.         specGiven)) {
  931.         if (HadValue < 0)    # error occured
  932.             return HadValue
  933.         if (HadValue == 2)
  934.             ArgInd++    # Account for the single-char value we used.
  935.         else {
  936.             if (NeedNextOpt) {    # option took next arg as value
  937.             delete argv[++ArgNum]
  938.             ArgsLeft--
  939.             }
  940.             break    # This option has been used up
  941.         }
  942.         }
  943.     }
  944.     if (Escape)
  945.         break
  946.     # Do not delete arg until after processing of it, so that if it is not
  947.     # recognized it can be left in ARGV[].
  948.     delete argv[ArgNum]
  949.     ArgsLeft--
  950.     }
  951.     if (compress != 0) {
  952.     dest = 1
  953.     src = argc - ArgsLeft + 1
  954.     for (count = ArgsLeft - 1; count; count--) {
  955.         ARGV[dest] = ARGV[src]
  956.         dest++
  957.         src++
  958.     }
  959.     }
  960.     return ArgsLeft
  961. }
  962.  
  963. # Assignment to values in Options[] occurs only in this function.
  964. # Option: Option specifier character.
  965. # Value: Value to be assigned to option, if it takes a value.
  966. # Options[]: Options array to return values in.
  967. # ArgType: Argument type specifier character.
  968. # GotValue: Whether any value is available to be assigned to this option.
  969. # Name: Name of option being processed.
  970. # OptionNum: Number of this option (starting with 1) if set in argv[],
  971. #     or 0 if it was given in a config file or in the environment.
  972. # SingleOpt: true if the value (if any) that is available for this option was
  973. #     given as part of the same command line arg as the option.  Used only for
  974. #     options from the command line.
  975. # specGiven is the option specifier character use, if any (e.g. - or +),
  976. # for use in error messages.
  977. # Global variables: OptErr
  978. # Return value: negative value on error, 0 if option did not require an
  979. # argument, 1 if it did & used the whole arg, 2 if it required just one char of
  980. # the arg.
  981. # Current error values:
  982. # -1: Option that required an argument did not get it.
  983. # -2: Value of incorrect type supplied for option.
  984. # -3: Bad type given for option &
  985. function AssignVal(Option,Value,Options,ArgType,GotValue,Name,OptionNum,
  986. SingleOpt,specGiven,  UsedValue,Err,NumTypes) {
  987.     # If option takes a value...    [
  988.     NumTypes = "*()#<>]"
  989.     if (Option == "&" && ArgType !~ "[" NumTypes) {    # ]
  990.     OptErr = "Bad type given for & option"
  991.     return -3
  992.     }
  993.  
  994.     if (UsedValue = (ArgType ~ "[:;" NumTypes)) {    # ]
  995.     if (!GotValue) {
  996.         if (Name != "")
  997.         OptErr = "Variable requires a value -- " Name
  998.         else
  999.         OptErr = "option requires an argument -- " Option
  1000.         return -1
  1001.     }
  1002.     if ((Err = CheckType(ArgType,Value,Option,Name,specGiven)) != "") {
  1003.         OptErr = Err
  1004.         return -2
  1005.     }
  1006.     # Mark this as a numeric variable; will be propogated to Options[] val.
  1007.     if (ArgType != ":" && ArgType != ";")
  1008.         Value += 0
  1009.     if ((Instance = ++Options[Option,"count"]) > 1)
  1010.         Options[Option,Instance] = Value
  1011.     else
  1012.         Options[Option] = Value
  1013.     }
  1014.     # If this is an environ or rcfile assignment & it was given a value...
  1015.     else if (!OptionNum && Value != "") {
  1016.     UsedValue = 1
  1017.     # If the value is "0" or "-" and this is the first instance of it,
  1018.     # do not set Options[Option]; this allows an assignment in an rcfile to
  1019.     # turn off an option (for the simple "Option in Options" test) in such
  1020.     # a way that it cannot be turned on in a later file.
  1021.     if (!(Option in Options) && (Value == "0" || Value == "-"))
  1022.         Instance = 1
  1023.     else
  1024.         Instance = ++Options[Option]
  1025.     # Save the value even though this is a flag
  1026.     Options[Option,Instance] = Value
  1027.     }
  1028.     # If this is a command line flag and has a - following it in the same arg,
  1029.     # it is being turned off.
  1030.     else if (OptionNum && SingleOpt && substr(Value,1,1) == "-") {
  1031.     UsedValue = 2
  1032.     if (Option in Options)
  1033.         Instance = ++Options[Option]
  1034.     else
  1035.         Instance = 1
  1036.     Options[Option,Instance]
  1037.     }
  1038.     # If this is a flag assignment without a value, increment the count for the
  1039.     # flag unless it was turned off.  The indicator for a flag being turned off
  1040.     # is that the flag index has not been set in Options[] but it has an
  1041.     # instance count.
  1042.     else if (Option in Options || !((Option,1) in Options))
  1043.     # Increment number of times this flag seen; will inc null value to 1
  1044.     Instance = ++Options[Option]
  1045.     Options[Option,"num",Instance] = OptionNum
  1046.     return UsedValue
  1047. }
  1048.  
  1049. # Option is the option letter
  1050. # Value is the value being assigned
  1051. # Name is the var name of the option, if any
  1052. # ArgType is one of:
  1053. # :    String argument
  1054. # ;    Non-null string argument
  1055. # *    Floating point argument
  1056. # (    Non-negative floating point argument
  1057. # )    Positive floating point argument
  1058. # #    Integer argument
  1059. # <    Non-negative integer argument
  1060. # >    Positive integer argument
  1061. # specGiven is the option specifier character use, if any (e.g. - or +),
  1062. # for use in error messages.
  1063. # Returns null on success, err string on error
  1064. function CheckType(ArgType,Value,Option,Name,specGiven,  Err,ErrStr) {
  1065.     if (ArgType == ":")
  1066.     return ""
  1067.     if (ArgType == ";") {
  1068.     if (Value == "")
  1069.         Err = "must be a non-empty string"
  1070.     }
  1071.     # A number begins with optional + or -, and is followed by a string of
  1072.     # digits or a decimal with digits before it, after it, or both
  1073.     else if (Value !~ /^[-+]?([0-9]+|[0-9]*\.[0-9]+|[0-9]+\.)$/)
  1074.     Err = "must be a number"
  1075.     else if (ArgType ~ "[#<>]" && Value ~ /\./)
  1076.     Err = "may not include a fraction"
  1077.     else if (ArgType ~ "[()<>]" && Value < 0)
  1078.     Err = "may not be negative"
  1079.     # (
  1080.     else if (ArgType ~ "[)>]" && Value == 0)
  1081.     Err = "must be a positive number"
  1082.     if (Err != "") {
  1083.     ErrStr = "Bad value \"" Value "\".  Value assigned to "
  1084.     if (Name != "")
  1085.         return ErrStr "variable " substr(Name,1,1) " " Err
  1086.     else {
  1087.         if (Option == "&")
  1088.         Option = Value
  1089.         return ErrStr "option " specGiven substr(Option,1,1) " " Err
  1090.     }
  1091.     }
  1092.     else
  1093.     return ""
  1094. }
  1095.  
  1096. # Note: only the above functions are needed by ProcArgs.
  1097. # The rest of these functions call ProcArgs() and also do other
  1098. # option-processing stuff.
  1099.  
  1100. # Opts: Process command line arguments.
  1101. # Opts processes command line arguments using ProcArgs()
  1102. # and checks for errors.  If an error occurs, a message is printed
  1103. # and the program is exited.
  1104. #
  1105. # Input variables:
  1106. # Name is the name of the program, for error messages.
  1107. # Usage is a usage message, for error messages.
  1108. # OptList the option description string, as used by ProcArgs().
  1109. # MinArgs is the minimum number of non-option arguments that this
  1110. # program should have, non including ARGV[0] and +h.
  1111. # If the program does not require any non-option arguments,
  1112. # MinArgs should be omitted or given as 0.
  1113. # rcFiles, if given, is a colon-seprated list of filenames to read for
  1114. # variable initialization.  If a filename begins with ~/, the ~ is replaced
  1115. # by the value of the environment variable HOME.  If a filename begins with
  1116. # $, the part from the character after the $ up until (but not including)
  1117. # the first character not in [a-zA-Z0-9_] will be searched for in the
  1118. # environment; if found its value will be substituted, if not the filename will
  1119. # be discarded.
  1120. # rcfiles are read in the order given.
  1121. # Values given in them will not override values given on the command line,
  1122. # and values given in later files will not override those set in earlier
  1123. # files, because AssignVal() will store each with a different instance index.
  1124. # The first instance of each variable, either on the command line or in an
  1125. # rcfile, will be stored with no instance index, and this is the value
  1126. # normally used by programs that call this function.
  1127. # VarNames is a comma-separated list of variable names to map to options,
  1128. # in the same order as the options are given in OptList.
  1129. # If EnvSearch is given and nonzero, the first EnvSearch variables will also be
  1130. # searched for in the environment.  If set to -1, all values will be searched
  1131. # for in the environment.  Values given in the environment will override
  1132. # those given in the rcfiles but not those given on the command line.
  1133. # NoRCopt, if given, is an additional letter option that if given on the
  1134. # command line prevents the rcfiles from being read.
  1135. # See ProcArgs() for a description of AllowUnRecOpt and optChars, and
  1136. # ExclusiveOptions() for a description of exOpts.
  1137. # Special options:
  1138. # If x is made an option and is given, some debugging info is output.
  1139. # h is assumed to be the help option.
  1140.  
  1141. # Global variables:
  1142. # The command line arguments are taken from ARGV[].
  1143. # The arguments that are option specifiers and values are removed from
  1144. # ARGV[], leaving only ARGV[0] and the non-option arguments.
  1145. # The number of elements in ARGV[] should be in ARGC.
  1146. # After processing, ARGC is set to the number of elements left in ARGV[].
  1147. # The option values are put in Options[].
  1148. # On error, Err is set to a positive integer value so it can be checked for in
  1149. # an END block.
  1150. # Return value: The number of elements left in ARGV is returned.
  1151. # Must keep OptErr global since it may be set by InitOpts().
  1152. function Opts(Name,Usage,OptList,MinArgs,rcFiles,VarNames,EnvSearch,NoRCopt,
  1153. AllowUnrecOpt,optChars,exOpts,  ArgsLeft,e) {
  1154.     if (MinArgs == "")
  1155.     MinArgs = 0
  1156.     ArgsLeft = ProcArgs(ARGC,ARGV,OptList NoRCopt,Options,1,AllowUnrecOpt,
  1157.     optChars)
  1158.     if (ArgsLeft < (MinArgs+1) && !("h" in Options)) {
  1159.     if (ArgsLeft >= 0) {
  1160.         OptErr = "Not enough arguments"
  1161.         Err = 4
  1162.     }
  1163.     else
  1164.         Err = -ArgsLeft
  1165.     printf "%s: %s.\nUse -h for help.\n%s\n",
  1166.     Name,OptErr,Usage > "/dev/stderr"
  1167.     exit 1
  1168.     }
  1169.     if (rcFiles != "" && (NoRCopt == "" || !(NoRCopt in Options)) &&
  1170.     (e = InitOpts(rcFiles,Options,OptList,VarNames,EnvSearch)) < 0)
  1171.     {
  1172.     print Name ": " OptErr ".\nUse -h for help." > "/dev/stderr"
  1173.     Err = -e
  1174.     exit 1
  1175.     }
  1176.     if ((exOpts != "") && ((OptErr = ExclusiveOptions(exOpts,Options)) != ""))
  1177.     {
  1178.     printf "%s: Error: %s\n",Name,OptErr > "/dev/stderr"
  1179.     Err = 1
  1180.     exit 1
  1181.     }
  1182.     return ArgsLeft
  1183. }
  1184.  
  1185. # ReadConfFile(): Read a file containing var/value assignments, in the form
  1186. # <variable-name><assignment-char><value>.
  1187. # Whitespace (spaces and tabs) around a variable (leading whitespace on the
  1188. # line and whitespace between the variable name and the assignment character) 
  1189. # is stripped.  Lines that do not contain an assignment operator or which
  1190. # contain a null variable name are ignored, other than possibly being noted in
  1191. # the return value.  If more than one assignment is made to a variable, the
  1192. # first assignment is used.
  1193. # Input variables:
  1194. # File is the file to read.
  1195. # Comment is the line-comment character.  If it is found as the first non-
  1196. #     whitespace character on a line, the line is ignored.
  1197. # Assign is the assignment string.  The first instance of Assign on a line
  1198. #     separates the variable name from its value.
  1199. # If StripWhite is true, whitespace around the value (whitespace between the
  1200. #     assignment char and trailing whitespace on the line) is stripped.
  1201. # VarPat is a pattern that variable names must match.  
  1202. #     Example: "^[a-zA-Z][a-zA-Z0-9]+$"
  1203. # If FlagsOK is true, variables are allowed to be "set" by being put alone on
  1204. #     a line; no assignment operator is needed.  These variables are set in
  1205. #     the output array with a null value.  Lines containing nothing but
  1206. #     whitespace are still ignored.
  1207. # Output variables:
  1208. # Values[] contains the assignments, with the indexes being the variable names
  1209. #     and the values being the assigned values.
  1210. # Lines[] contains the line number that each variable occured on.  A flag set
  1211. #     is record by giving it an index in Lines[] but not in Values[].
  1212. # Return value:
  1213. # If any errors occur, a string consisting of descriptions of the errors
  1214. # separated by newlines is returned.  In no case will the string start with a
  1215. # numeric value.  If no errors occur,  the number of lines read is returned.
  1216. function ReadConfigFile(Values,Lines,File,Comment,Assign,StripWhite,VarPat,
  1217. FlagsOK,
  1218. Line,Status,Errs,AssignLen,LineNum,Var,Val) {
  1219.     if (Comment != "")
  1220.     Comment = "^" Comment
  1221.     AssignLen = length(Assign)
  1222.     if (VarPat == "")
  1223.     VarPat = "."    # null varname not allowed
  1224.     while ((Status = (getline Line < File)) == 1) {
  1225.     LineNum++
  1226.     sub("^[ \t]+","",Line)
  1227.     if (Line == "")        # blank line
  1228.         continue
  1229.     if (Comment != "" && Line ~ Comment)
  1230.         continue
  1231.     if (Pos = index(Line,Assign)) {
  1232.         Var = substr(Line,1,Pos-1)
  1233.         Val = substr(Line,Pos+AssignLen)
  1234.         if (StripWhite) {
  1235.         sub("^[ \t]+","",Val)
  1236.         sub("[ \t]+$","",Val)
  1237.         }
  1238.     }
  1239.     else {
  1240.         Var = Line    # If no value, var is entire line
  1241.         Val = ""
  1242.     }
  1243.     if (!FlagsOK && Val == "") {
  1244.         Errs = Errs \
  1245.         sprintf("\nBad assignment on line %d of file %s: %s",
  1246.         LineNum,File,Line)
  1247.         continue
  1248.     }
  1249.     sub("[ \t]+$","",Var)
  1250.     if (Var !~ VarPat) {
  1251.         Errs = Errs sprintf("\nBad variable name on line %d of file %s: %s",
  1252.         LineNum,File,Var)
  1253.         continue
  1254.     }
  1255.     if (!(Var in Lines)) {
  1256.         Lines[Var] = LineNum
  1257.         if (Pos)
  1258.         Values[Var] = Val
  1259.     }
  1260.     }
  1261.     if (Status)
  1262.     Errs = Errs "\nCould not read file " File
  1263.     close(File)
  1264.     return Errs == "" ? LineNum : substr(Errs,2)    # Skip first newline
  1265. }
  1266.  
  1267. # Variables:
  1268. # Data is stored in Options[].
  1269. # rcFiles, OptList, VarNames, and EnvSearch are as as described for Opts().
  1270. # Global vars:
  1271. # Sets OptErr.  Uses ENVIRON[].
  1272. # If anything is read from any of the rcfiles, sets READ_RCFILE to 1.
  1273. function InitOpts(rcFiles,Options,OptList,VarNames,EnvSearch,
  1274. Line,Var,Pos,Vars,Map,CharOpt,NumVars,TypesInd,Types,Type,Ret,i,rcFile,
  1275. fNames,numrcFiles,filesRead,Err,Values,retStr) {
  1276.     split("",filesRead,"")    # make awk know this is an array
  1277.     NumVars = split(VarNames,Vars,",")
  1278.     TypesInd = Ret = 0
  1279.     if (EnvSearch == -1)
  1280.     EnvSearch = NumVars
  1281.     for (i = 1; i <= NumVars; i++) {
  1282.     Var = Vars[i]
  1283.     CharOpt = substr(OptList,++TypesInd,1)
  1284.     if (CharOpt ~ "^[:;*()#<>&]$")
  1285.         CharOpt = substr(OptList,++TypesInd,1)
  1286.     Map[Var] = CharOpt
  1287.     Types[Var] = Type = substr(OptList,TypesInd+1,1)
  1288.     # Do not overwrite entries from environment
  1289.     if (i <= EnvSearch && Var in ENVIRON &&
  1290.     (Err = AssignVal(CharOpt,ENVIRON[Var],Options,Type,1,Var,0)) < 0)
  1291.         return Err
  1292.     }
  1293.  
  1294.     numrcFiles = split(rcFiles,fNames,":")
  1295.     for (i = 1; i <= numrcFiles; i++) {
  1296.     rcFile = fNames[i]
  1297.     if (rcFile ~ "^~/")
  1298.         rcFile = ENVIRON["HOME"] substr(rcFile,2)
  1299.     else if (rcFile ~ /^\$/) {
  1300.         rcFile = substr(rcFile,2)
  1301.         match(rcFile,"^[a-zA-Z0-9_]*")
  1302.         envvar = substr(rcFile,1,RLENGTH)
  1303.         if (envvar in ENVIRON)
  1304.         rcFile = ENVIRON[envvar] substr(rcFile,RLENGTH+1)
  1305.         else
  1306.         continue
  1307.     }
  1308.     if (rcFile in filesRead)
  1309.         continue
  1310.     # rcfiles are liable to be given more than once, e.g. UHOME and HOME
  1311.     # may be the same
  1312.     filesRead[rcFile]
  1313.     if ("x" in Options)
  1314.         printf "Reading configuration file %s\n",rcFile > "/dev/stderr"
  1315.     retStr = ReadConfigFile(Values,Lines,rcFile,"#","=",0,"",1)
  1316.     if (retStr > 0)
  1317.         READ_RCFILE = 1
  1318.     else if (ret != "") {
  1319.         OptErr = retStr
  1320.         Ret = -1
  1321.     }
  1322.     for (Var in Lines)
  1323.         if (Var in Map) {
  1324.         if ((Err = AssignVal(Map[Var],
  1325.         Var in Values ? Values[Var] : "",Options,Types[Var],
  1326.         Var in Values,Var,0)) < 0)
  1327.             return Err
  1328.         }
  1329.         else {
  1330.         OptErr = sprintf(\
  1331.         "Unknown var \"%s\" assigned to on line %d\nof file %s",Var,
  1332.         Lines[Var],rcFile)
  1333.         Ret = -1
  1334.         }
  1335.     }
  1336.  
  1337.     if ("x" in Options)
  1338.     for (Var in Map)
  1339.         if (Map[Var] in Options)
  1340.         printf "(%s) %s=%s\n",Map[Var],Var,Options[Map[Var]] > \
  1341.         "/dev/stderr"
  1342.         else
  1343.         printf "(%s) %s not set\n",Map[Var],Var > "/dev/stderr"
  1344.     return Ret
  1345. }
  1346.  
  1347. # OptSets is a semicolon-separated list of sets of option sets.
  1348. # Within a list of option sets, the option sets are separated by commas.  For
  1349. # each set of sets, if any option in one of the sets is in Options[] AND any
  1350. # option in one of the other sets is in Options[], an error string is returned.
  1351. # If no conflicts are found, nothing is returned.
  1352. # Example: if OptSets = "ab,def,g;i,j", an error will be returned due to
  1353. # the exclusions presented by the first set of sets (ab,def,g) if:
  1354. # (a or b is in Options[]) AND (d, e, or f is in Options[]) OR
  1355. # (a or b is in Options[]) AND (g is in Options) OR
  1356. # (d, e, or f is in Options[]) AND (g is in Options)
  1357. # An error will be returned due to the exclusions presented by the second set
  1358. # of sets (i,j) if: (i is in Options[]) AND (j is in Options[]).
  1359. # todo: make options given on command line unset options given in config file
  1360. # todo: that they conflict with.
  1361. function ExclusiveOptions(OptSets,Options,
  1362. Sets,SetSet,NumSets,Pos1,Pos2,Len,s1,s2,c1,c2,ErrStr,L1,L2,SetSets,NumSetSets,
  1363. SetNum,OSetNum) {
  1364.     NumSetSets = split(OptSets,SetSets,";")
  1365.     # For each set of sets...
  1366.     for (SetSet = 1; SetSet <= NumSetSets; SetSet++) {
  1367.     # NumSets is the number of sets in this set of sets.
  1368.     NumSets = split(SetSets[SetSet],Sets,",")
  1369.     # For each set in a set of sets except the last...
  1370.     for (SetNum = 1; SetNum < NumSets; SetNum++) {
  1371.         s1 = Sets[SetNum]
  1372.         L1 = length(s1)
  1373.         for (Pos1 = 1; Pos1 <= L1; Pos1++)
  1374.         # If any of the options in this set was given, check whether
  1375.         # any of the options in the other sets was given.  Only check
  1376.         # later sets since earlier sets will have already been checked
  1377.         # against this set.
  1378.         if ((c1 = substr(s1,Pos1,1)) in Options)
  1379.             for (OSetNum = SetNum+1; OSetNum <= NumSets; OSetNum++) {
  1380.             s2 = Sets[OSetNum]
  1381.             L2 = length(s2)
  1382.             for (Pos2 = 1; Pos2 <= L2; Pos2++)
  1383.                 if ((c2 = substr(s2,Pos2,1)) in Options)
  1384.                 ErrStr = ErrStr "\n"\
  1385.                 sprintf("Cannot give both %s and %s options.",
  1386.                 c1,c2)
  1387.             }
  1388.     }
  1389.     }
  1390.     if (ErrStr != "")
  1391.     return substr(ErrStr,2)
  1392.     return ""
  1393. }
  1394.  
  1395. # The value of each instance of option Opt that occurs in Options[] is made an
  1396. # index of Set[].
  1397. # The return value is the number of instances of Opt in Options.
  1398. function Opt2Set(Options,Opt,Set,  count) {
  1399.     if (!(Opt in Options))
  1400.     return 0
  1401.     Set[Options[Opt]]
  1402.     count = Options[Opt,"count"]
  1403.     for (; count > 1; count--)
  1404.     Set[Options[Opt,count]]
  1405.     return count
  1406. }
  1407.  
  1408. # The value of each instance of option Opt that occurs in Options[] that
  1409. # begins with "!" is made an index of nSet[] (with the ! stripped from it).
  1410. # Other values are made indexes of Set[].
  1411. # The return value is the number of instances of Opt in Options.
  1412. function Opt2Sets(Options,Opt,Set,nSet,  count,aSet,ret) {
  1413.     ret = Opt2Set(Options,Opt,aSet)
  1414.     for (value in aSet)
  1415.     if (substr(value,1,1) == "!")
  1416.         nSet[substr(value,2)]
  1417.     else
  1418.         Set[value]
  1419.     return ret
  1420. }
  1421.  
  1422. # Returns true if option Opt was given on the command line.
  1423. function CmdLineOpt(Options,Opt,  i) {
  1424.     for (i = 1; (Opt,"num",i) in Options; i++)
  1425.     if (Options[Opt,"num",i] != 0)
  1426.         return 1
  1427.     return 0
  1428. }
  1429. ### End of ProcArgs library
  1430. ### Begin set library
  1431. # 96/05/23 added return values  jhdiii
  1432. # 96/05/25 added set2list()
  1433.  
  1434. # Return value: the number of new elements added to Inter
  1435. function Intersection(A,B,Inter,  Elem,Count) {
  1436.     for (Elem in A)
  1437.     if (Elem in B && !(Elem in Inter)) {
  1438.         Inter[Elem]
  1439.         Count++
  1440.     }
  1441.     return Count
  1442. }
  1443.  
  1444. # Return value: the number of new elements added to Both
  1445. function Union(A,B,Both) {
  1446.     return CopySet(A,Both) + CopySet(B,Both)
  1447. }
  1448.  
  1449. # Deletes any elements that are in both Minuend and Subtrahend from Minuend.
  1450. # Return value: the number of elements deleted.
  1451. function SubtractSet(Minuend,Subtrahend,  Elem,nDel) {
  1452.     for (Elem in Subtrahend)
  1453.     if (Elem in Minuend) {
  1454.         delete Minuend[Elem]
  1455.         nDel++
  1456.     }
  1457.     return nDel
  1458. }
  1459.  
  1460. # Return value: the number of new elements added to To
  1461. function CopySet(From,To,  Elem,n) {
  1462.     for (Elem in From)
  1463.     if (!(Elem in To)) {
  1464.         To[Elem]
  1465.         n++
  1466.     }
  1467.     return n
  1468. }
  1469.  
  1470. # Returns 1 if Set is empty, 0 if not.
  1471. function IsEmpty(Set,  i) {
  1472.     for (i in Set)
  1473.     return 0
  1474.     return 1
  1475. }
  1476.  
  1477. # MakeSet: make a set from a list.
  1478. # An index with the name of each element of the list is created in the given
  1479. # array.
  1480. # Input variables: 
  1481. # Elements is a string containing the list of elements.
  1482. # Sep is the character that separates the elements of the list.
  1483. # Output variables:
  1484. # Set is the array.
  1485. # Return value: the number of new elements added to the set.
  1486. function MakeSet(Set,Elements,Sep,  i,Num,Names,nFound,ind) {
  1487.     nFound = 0
  1488.     Num = split(Elements,Names,Sep)
  1489.     for (i = 1; i <= Num; i++) {
  1490.     ind = Names[i]
  1491.     if (!(ind in Set)) {
  1492.         Set[ind]
  1493.         nFound++
  1494.     }
  1495.     }
  1496.     return nFound
  1497. }
  1498.  
  1499. # Returns the number of elements in set Set
  1500. function NumElem(Set,  elem,Num) {
  1501.     for (elem in Set)
  1502.     Num++
  1503.     return Num
  1504. }
  1505.  
  1506. # Remove all elements from Set
  1507. function DeleteAll(Set,  i) {
  1508.     split("",Set,",")
  1509. }
  1510.  
  1511. # Returns a list of all of the elements in Set[], with each pair of elements
  1512. # separated by Sep.
  1513. function set2list(Set,Sep,  list,elem) {
  1514.     for (elem in Set)
  1515.     list = list Sep elem
  1516.     return substr(list,2)    # skip 1st separator
  1517. }
  1518. ### End set library
  1519. ### start canonTTY library
  1520. function nodevTTY(tty) {
  1521.     sub("^/dev/","",tty)
  1522.     return tty
  1523. }
  1524.  
  1525. function canonTTY(tty) {
  1526.     if (tty ~ "^/dev/")
  1527.     sub("^/dev/","",tty)
  1528.     else if (tty !~ /^tty/)
  1529.     tty = "tty" tty
  1530.     return tty
  1531. }
  1532.  
  1533. function shortTTY(tty) {
  1534.     sub("^/dev/","",tty)
  1535.     sub("^tty","",tty)
  1536.     return tty
  1537. }
  1538. ### end canonTTY library
  1539. function basename(path) {
  1540.     sub(".*/","",path)
  1541.     return path
  1542. }
  1543. ### Start of tinfo lib
  1544. # @(#) tinfo 1.0 96/11/30
  1545. # altInit(): Get alternate character set terminfo capabilities.
  1546. # term, noerror: see tiget().
  1547. # tinfo: contains the acsc capability, and any of the enacs, smacs, and rmacs
  1548. # capabilities that are defined for the terminal.  Each is indexed by its
  1549. # capability name.  enacs is used to enable the alternate character set;
  1550. # smacs starts it; rmacs ends it.  acsc is the mapping of vt100 alternate
  1551. # character codes to those appropriate for the given terminal.
  1552. # AltMap is the acsc string broken down with each alternate character indexed
  1553. # by its vt100 equivalent.  num is an ordered list of the vt100 characters
  1554. # indexed starting with 1, for applications that need to know what order they
  1555. # were given in.
  1556. # The global _macs[] is set up with _macs[0] = rmacs & _macs[1] = smacs, for
  1557. # use by altPrint().
  1558. # The alternate characters and their indexes (vt100 equivalents) are:
  1559. # 0  solid square block        a  checker board    f  degree symbol
  1560. # g  plus/minus            h  board of squares    j  lower right corner
  1561. # k  upper right corner        l  upper left corner    m  lower left corner
  1562. # n  plus            q  horizontal line    t  left tee
  1563. # u  right tee            v  bottom tee        w  top tee
  1564. # x  vertical line        +  arrow pointing right    .  arrow pointing down
  1565. # -  arrow pointing up        ,  arrow pointing left    `  diamond
  1566. # ~  bullet            I  lantern symbol    o  scan line 1
  1567. # s  scan line 9
  1568. function altInit(tinfo,term,noerror,AltMap,num,  ret,caplist,acsc,len,j,i) {
  1569.     if (ret = tiget("acsc",tinfo,term)) {
  1570.     # All other types of errors cause tput to print an informative message
  1571.     # to stderr, which is not redirected.
  1572.     if (!noerror && ret == 1)
  1573.         print "Terminal has no acsc capability." > "/dev/stderr"
  1574.     return ret
  1575.     }
  1576.     caplist = "enacs,smacs,rmacs"
  1577.     tiget(caplist,tinfo,term)
  1578.     acsc = tinfo["acsc"]
  1579.     len = length(acsc)
  1580.     j = 0
  1581.     for (i = 1; i < len; i += 2)
  1582.     AltMap[num[++j] = substr(acsc,i,1)] = substr(acsc,i+1,1)
  1583.     if ("rmacs" in tinfo)
  1584.     _macs[0] = tinfo["rmacs"]
  1585.     if ("smacs" in tinfo)
  1586.     _macs[1] = tinfo["smacs"]
  1587. }
  1588.  
  1589. # altPrint: Print characters in either the alternate or standard character set.
  1590. # string is the string to print.
  1591. # alt should be 1 if string is in the alternate character set; 0 if in the
  1592. # standard character set.
  1593. # tinfo contains the smacs and rmacs strings, if needed.
  1594. # altPrint keeps track of whether the terminal is in the standard or alternate
  1595. # character set, and issues smacs and rmacs as needed.
  1596. # It should always be called with alt false at the end of program execution to
  1597. # ensure that the terminal is left in the standard character set.
  1598. # Globals: The character set is tracked in _altPrintSet
  1599. function altPrint(string,alt,tinfo) {
  1600.     if (alt != _altPrintSet) {
  1601.     printf "%s%s",_macs[alt],string
  1602.     _altPrintSet = alt
  1603.     }
  1604.     else
  1605.     printf "%s",string
  1606. }
  1607.  
  1608. # tiget: get terminfo capabilities.
  1609. # capnames is a comma-separated list of terminfo capabilities to get.
  1610. # Each capability is put in tinfo[], indexed by capability name.
  1611. # If term is passed, it is the terminal type to get the capabilities for.
  1612. # If not, the value of the environment variable TERM is used.
  1613. # If noerror is true, error messages are suppressed.
  1614. # Return value: the exit status of the last tput, or -1 if term is not passed
  1615. # and there is no TERM environment variable.
  1616. function tiget(capnames,tinfo,term,noerror,  cmd,RS,ret,names,capname,i) {
  1617.     if (term == "")
  1618.     if ("TERM" in ENVIRON)
  1619.         term = ENVIRON["TERM"]
  1620.     else
  1621.         return -1
  1622.     split(capnames,names,",")
  1623.     RS = ""    # this makes the record separator be "\n\n", which hopefully
  1624.         # is not very common in terminfo capabilities
  1625.     for (i = 1; i in names; i++) {
  1626.     capname = names[i]
  1627.     cmd = "exec tput -T " term " " capname
  1628.     if (noerror)
  1629.         cmd = cmd " 2>/dev/null"
  1630.     cmd | getline
  1631.     if (!(ret = close(cmd)))
  1632.         # printf interprets many of the escape chars in the same manner that
  1633.         # the terminfo library does... not perfect, but better than nothing
  1634.         tinfo[capname] = sprintf($0)
  1635.     }
  1636.     return ret
  1637. }
  1638. ### End of tinfo lib
  1639. ### Begin qsort routines
  1640.  
  1641. # Arr[] is an array of values with arbitrary indices.
  1642. # k[] is returned with numeric indices 1..n.
  1643. # The values in k[] are the indices of Arr[],
  1644. # ordered so that if Arr[] is stepped through
  1645. # in the order Arr[k[1]] .. Arr[k[n]], it will be stepped
  1646. # through in order of the values of its elements.
  1647. # The return value is the number of elements in the arrays (n).
  1648. function qsortArbIndByValue(Arr,k,  ArrInd,ElNum) {
  1649.     ElNum = 0
  1650.     for (ArrInd in Arr)
  1651.     k[++ElNum] = ArrInd
  1652.     qsortSegment(Arr,k,1,ElNum)
  1653.     return ElNum
  1654. }
  1655.  
  1656. # Sort a segment of an array.
  1657. # Arr[] contains data with arbitrary indices.
  1658. # k[] has indices 1..nelem, with the indices of arr[] as values.
  1659. # This function sorts the elements of arr that are pointed to by
  1660. # k[start..end], swapping the values of elements of k[] so that
  1661. # when this function returns arr[k[start..end]] will be in order.
  1662. function qsortSegment(Arr,k,start,end,  left,right,sepval,tmp,tmpe,tmps) {
  1663.     # handle two-element case explicitly for a tiny speedup
  1664.     if ((end - start) == 1) {
  1665.     if (Arr[tmps = k[start]] > Arr[tmpe = k[end]]) {
  1666.         k[start] = tmpe
  1667.         k[end] = tmps
  1668.     }
  1669.     return
  1670.     }
  1671.     # Make sure comparisons act on these as numbers
  1672.     left = start+0
  1673.     right = end+0
  1674.     sepval = Arr[k[int((left + right) / 2)]]
  1675.     # Make every element <= sepval be to the left of every element > sepval
  1676.     while (left < right) {
  1677.     while (Arr[k[left]] < sepval)
  1678.         left++
  1679.     while (Arr[k[right]] > sepval)
  1680.         right--
  1681.     if (left < right) {
  1682.         tmp = k[left]
  1683.         k[left++] = k[right]
  1684.         k[right--] = tmp
  1685.     }
  1686.     }
  1687.     if (left == right)
  1688.     if (Arr[k[left]] < sepval)
  1689.         left++
  1690.     else
  1691.         right--
  1692.     if (start < right)
  1693.     qsortSegment(Arr,k,start,right)
  1694.     if (left < end)
  1695.     qsortSegment(Arr,k,left,end)
  1696. }
  1697.  
  1698. # Arr[] is an array of values with arbitrary indices.
  1699. # k[] is returned with numeric indices 1..n.
  1700. # The values in k are the indices of Arr[],
  1701. # ordered so that if Arr[] is stepped through
  1702. # in the order Arr[k[1]] .. Arr[k[n]], it will be stepped
  1703. # through in order of the values of its indices.
  1704. # The return value is the number of elements in the arrays (n).
  1705. # If the indexes are numeric, Numeric should be true, so that they can be
  1706. # compared as such rather than as strings.  Numeric indexes do not have to be
  1707. # contiguous.
  1708. function qsortByArbIndex(Arr,k,Numeric,  ArrInd,ElNum) {
  1709.     ElNum = 0
  1710.     if (Numeric)
  1711.     # Indexes do not preserve numeric type, so must be forced
  1712.     for (ArrInd in Arr)
  1713.         k[++ElNum] = ArrInd+0
  1714.     else
  1715.     for (ArrInd in Arr)
  1716.         k[++ElNum] = ArrInd
  1717.     qsortNumIndByValue(k,1,ElNum)
  1718.     return ElNum
  1719. }
  1720.  
  1721. # Arr is an array of elements with contiguous numeric indexes to be sorted
  1722. # by value.
  1723. # start and end are the starting and ending indexes of the range to be sorted.
  1724. function qsortNumIndByValue(Arr,start,end,  left,right,sepval,tmp,tmpe,tmps) {
  1725.     # handle two-element case explicitly for a tiny speedup
  1726.     if ((start - end) == 1) {
  1727.     if ((tmps = Arr[start]) > (tmpe = Arr[end])) {
  1728.         Arr[start] = tmpe
  1729.         Arr[end] = tmps
  1730.     }
  1731.     return
  1732.     }
  1733.     left = start+0
  1734.     right = end+0
  1735.     sepval = Arr[int((left + right) / 2)]
  1736.     while (left < right) {
  1737.     while (Arr[left] < sepval)
  1738.         left++
  1739.     while (Arr[right] > sepval)
  1740.         right--
  1741.     if (left <= right) {
  1742.         tmp = Arr[left]
  1743.         Arr[left++] = Arr[right]
  1744.         Arr[right--] = tmp
  1745.     }
  1746.     }
  1747.     if (start < right)
  1748.     qsortNumIndByValue(Arr,start,right)
  1749.     if (left < end)
  1750.     qsortNumIndByValue(Arr,left,end)
  1751. }
  1752.  
  1753. ### End qsort routines
  1754. ### Start of DrawTrees lib
  1755. # @(#) DrawTrees 1.1 97/07/19
  1756. # 96/11/30 john h. dubois iii (john@armory.com)
  1757. # 97/07/19 1.1 Added preOrderTraversal(), indentation equalization.
  1758. #
  1759. #    Data[] is a tree of data to draw.  The indexes consist of one or more
  1760. # integer values separated by SUBSEP.  The "depth" of the element determines
  1761. # how many integers (dimensions) are contained in the index.  For each set
  1762. # of node siblings, the integer describing the varying dimension varies from
  1763. # 1 through n where n is the number of siblings.  This shows the indexes
  1764. # used for the elements of a small tree with depth 3:
  1765. # 1----+-1,1--+-1,1,1
  1766. #      |      |-1,2,2
  1767. #      |      \-1,2,3
  1768. #      \-1,2--+-1,2,1
  1769. #             \-1,2,2
  1770. # 2------2,1--+-2,1,1
  1771. #             \-2,2,2
  1772. #       ^----^--see below
  1773. # The values of the elements are lines of data which constitute the nodes of
  1774. # the tree.
  1775. #    Offset & Width: By default, the tree is drawn with each node on a separate
  1776. # line. Offset is the horizonal offset of each child from its parent.  It
  1777. # must be at least 1.  If Width is non-0, the tree is instead drawn with the
  1778. # first child of each parent immediately to the right of its parent.  Width
  1779. # is the number of characters allocated to the node data for each level.  If
  1780. # the data for an interior node is longer than Width, the value is truncated
  1781. # to Width-1 characters and a left-tee is appended to indicate the
  1782. # truncation, so Width should be at least two.  If this style is used,
  1783. # Offset is the number of characters of additional horizontal separation to
  1784. # use after the "split point"; in the example tree above, Width is set to 1,
  1785. # causing the addition of the characters at the positions marked by ^ on the
  1786. # "see below" line.
  1787. #    AltChars[]: The tree is drawn using box-drawing characters appropriate to
  1788. # the terminal if they are available, and a default set of ASCII characters if
  1789. # not.  If AltChars[] contains all of the following elements, they are used to
  1790. # draw the tree.  I is the index to use; A is the ASCII default.
  1791. # I A Description
  1792. # x | Vertical bar
  1793. # q - Horizontal bar
  1794. # l / top left corner
  1795. # m \ bottom left corner
  1796. # w + Top tee 
  1797. # t } Left tee
  1798. # + > Right arrow (optional)
  1799. # ~ * Bullet (optional)
  1800. # If AltChars[] does not contain all of these elements and the alternate
  1801. # character set it used, AltChars[] is returned filled in with the
  1802. # characters used to draw the tree.  The same array can then be passed back
  1803. # to DrawTrees(), avoiding the need for it to use tput again to get the
  1804. # terminal's alternate character set capabilities.
  1805. #    If Spaces is true, indentation is done with spaces only; the effect is to
  1806. # set all of the above characters to be a space.
  1807. #    If term is passed, it overrides the TERM environment variable.  Pass "dumb"
  1808. # to force the ASCII values to be used.  
  1809. #    If useArrow is true and the terminal has a right-arrow character defined,
  1810. # it is used for the branch character to the left of node data.
  1811. #    If maxLength is non-0, output lines are truncated to maxLength characters.
  1812. #    If AddInd is true, in the output each value is preceded by its index.
  1813. #    If Sort is true, the tree is sorted by the lexicographical values of its
  1814. # elements, and the qsort library must be included in the program.
  1815. #    If maxBranchInd is non-0, an attempt is made to equalize the total
  1816. # indentation of each line due to the tree-drawing characters by using a
  1817. # string of horizontal bar characters, so that the lines in Data[] will
  1818. # be aligned with each other.  Width must be set to 0.  If maxBranchInd is
  1819. # -1, all lines will be indented to the same distance that the furthest
  1820. # indented line is.  If maxBranchInd is positive, it places a limit on how
  1821. # much indentation equalization is done.  The value sets the number of
  1822. # indents (each indent being Offset characters long) that may be used.  If the
  1823. # deepest indent is less than or equal to the value given, the number of
  1824. # indents used will be the same as the deepest indent.  If the deepest
  1825. # indent is larger than the value given, those lines indented further will
  1826. # not line up, resulting in ragged output.  When scanning the tree to determine
  1827. # its maximum depth, the indexes are examined directly rather than doing a
  1828. # proper tree traversal, so Data[] should not contain any extraneous elements
  1829. # except an option "HEADER" element.  If Data["HEADER"] is set, it is printed
  1830. # blank-indented to the equalization distance.  If "HEADER" is set, it will be
  1831. # printed even if maxBranchInd is 0, but will only be aligned with the data
  1832. # for the root nodes.
  1833. # Return value:
  1834. # A negative value is returned on error.
  1835. function DrawTrees(Data,Offset,Width,AltChars,Spaces,term,useArrow,maxLength,
  1836. AddInd,Sort,maxBranchInd,
  1837. i,tinfo,Strings,smacs,rmacs,BranchIndent,BlankIndent,bTail,veBar,hoBar,bLeft,
  1838. topTee,lTee,arrow,bullet,WidthBar,OffsetBar,ind,elem,n,maxDepth,Header) {
  1839.     if (Spaces) {
  1840.     veBar = hoBar = bLeft = topTee = lTee = arrow = " "
  1841.     bullet = "*"
  1842.     }
  1843.     else {
  1844.     if ("x" in AltChars && "q" in AltChars && "m" in AltChars && \
  1845.     "w" in AltChars && "t" in AltChars) {
  1846.         tinfo["smacs"] = AltChars["smacs"]
  1847.         tinfo["rmacs"] = AltChars["rmacs"]
  1848.         if ("enacs" in AltChars)
  1849.         tinfo["enacs"] = AltChars["enacs"]
  1850.     }
  1851.     else
  1852.         altInit(tinfo,term,1,AltChars)
  1853.     if ("x" in AltChars && "q" in AltChars && "m" in AltChars && \
  1854.     "w" in AltChars && "t" in AltChars) {
  1855.         AltChars["smacs"] = smacs = Strings["smacs"] = tinfo["smacs"]
  1856.         AltChars["rmacs"] = rmacs = Strings["rmacs"] = tinfo["rmacs"]
  1857.         if ("enacs" in tinfo) {
  1858.         printf "%s",tinfo["enacs"]
  1859.         AltChars["enacs"] = tinfo["enacs"]
  1860.         }
  1861.         veBar = AltChars["x"]
  1862.         hoBar = AltChars["q"]
  1863.         bLeft = AltChars["m"]
  1864.         tLeft = AltChars["l"]
  1865.         topTee = AltChars["w"]
  1866.         lTee = AltChars["t"]
  1867.         arrow = "+" in AltChars ? AltChars["+"] : hoBar
  1868.         bullet = "~" in AltChars ? AltChars["~"] : lTee
  1869.     }
  1870.     else {
  1871.         # Do not attempt mixing of alt & regular char sets for tree drawing
  1872.         veBar = "|"
  1873.         hoBar = "-"
  1874.         bLeft = "\\"
  1875.         tLeft = "/"
  1876.         topTee = "+"        # {
  1877.         lTee = "}"
  1878.         arrow = ">"
  1879.         bullet = "*"
  1880.     }
  1881.     }
  1882.     # b: blank indent.  Will be preceded by newline & followed by branch char.
  1883.     # v: indent that includes a vertical branch on the left:  "|    "
  1884.     #    Will be preceded by newline or whitespace & followed by branch char.
  1885.     # l: lower left horizontal branch indent.                 "\---"
  1886.     # t: left tee horizontal branch indent.                   "}---"
  1887.     # l & t will be preceded by newline or whitespace & followed by either
  1888.     # branch tail (">") or indentation equalization.
  1889.     # p: Node padding.  Must be adjusted to fit, so is not
  1890.     #    surrounded by smacs/rmacs.  Preceded by node data; followed by branch.
  1891.     #    If Width is 0, the node padding will equal to the offset, so it is
  1892.     #    also used for unbranched indentation equalization.
  1893.     # bie: Branched indentation equalization.  Like p but with a top tee on the
  1894.     #    left.   "-+--"
  1895.     # n: Internode branch.  Preceded by branch; followed by node data.  "-->"
  1896.     # tn: Teed internode branch.  Preceded b/branch; followed b/node data."+->"
  1897.     # c: Node data truncation character.  Will be followed by branch char.
  1898.     # n, tn, and c are used only in "width" mode.
  1899.     # lt: Line truncation character.
  1900.     # bt: Branch tail.   ">"
  1901.     # r:  Tree root.  "/"
  1902.     for (i = Offset + Width; i > 0; i-=1) {
  1903.     BlankIndent = BlankIndent " "
  1904.     BranchIndent = BranchIndent hoBar
  1905.     }
  1906.     WidthIndent = substr(BlankIndent,1,Width)
  1907.     OffsetIndent = substr(BranchIndent,1,Offset)
  1908.     if (BranchIndent != "" && Offset > 1)
  1909.     bTail = useArrow ? arrow : hoBar
  1910.     Strings["c"] = smacs lTee
  1911.     Strings["lt"] = smacs bullet rmacs
  1912.     Strings["p"] = BranchIndent
  1913.     Strings["n"] = substr(BranchIndent,1,Offset-1) bTail rmacs
  1914.     Strings["tn"] = topTee substr(BranchIndent,1,Offset-2) bTail rmacs
  1915.  
  1916.     Strings["b"] = BlankIndent
  1917.     Strings["v"] = WidthIndent smacs veBar rmacs substr(BlankIndent,1,Offset-1)
  1918.     Strings["l"] = WidthIndent smacs bLeft substr(OffsetIndent,3)
  1919.     Strings["t"] = WidthIndent smacs lTee  substr(OffsetIndent,3)
  1920.     Strings["bt"] = bTail rmacs
  1921.  
  1922.     if ("HEADER" in Data)
  1923.     Header = Data["HEADER"]
  1924.     if (maxBranchInd) {
  1925.     if (Width)
  1926.         return -1
  1927.     Strings["bie"] = (Offset > 1 ? hoBar : "") topTee substr(BranchIndent,3)
  1928.     for (ind in Data) {
  1929.         n = split(ind,elem,SUBSEP)-1
  1930.         if (n > maxDepth)
  1931.         maxDepth = n
  1932.     }
  1933.     if (maxBranchInd > 0 && maxBranchInd < maxDepth)
  1934.         maxDepth = maxBranchInd
  1935.     for (i = 1; i < maxDepth; i++) {
  1936.         Strings["r"] = Strings["r"] BranchIndent
  1937.         if (Header != "")
  1938.         printf "%s",BlankIndent
  1939.     }
  1940.     Strings["r"] = \
  1941.     smacs tLeft substr(BranchIndent,3) Strings["r"] bTail rmacs
  1942.     if (Header != "")
  1943.         printf "%s",BlankIndent
  1944.     }
  1945.     if (Header != "")
  1946.     print Header
  1947.     dtTraverse(Data,"",Strings,0,"",Width,maxLength,Offset+Width,AddInd,Sort,
  1948.     maxDepth)
  1949.     return 0
  1950. }
  1951.  
  1952. # dtTraverse(): Traverse and print a subtree.
  1953. # Data[], Width, AddInd, Sort: as described for DrawTrees().
  1954. # catind: index into Data[] for the parent of this node, followed by a SUBSEP
  1955. #    char.
  1956. # Strings[]: Line drawing characters, and rmacs/smacs strings.
  1957. # level: The depth of this node, with tree roots at level 0.
  1958. # branch: An indentation string to print the vertical components of the
  1959. #    branches of the siblings of the parents of this node.
  1960. # Length: How many further characters may be added at this indentation level.
  1961. # levelWidth: How many characters the indentation for each level occupies.
  1962. # equIndent: How much levels' worth of indentation equalization to do.
  1963. function dtTraverse(Data,catind,Strings,level,branch,Width,Length,levelWidth,
  1964. AddInd,Sort,equIndent,
  1965. childNum,ind,siblings,children,nbranch,len,s,subLength,value,k,Arr,i) {
  1966.     if (Length && (subLength = Length - levelWidth) < 1)
  1967.     # Make sure subLength does not end up 0, which indicates no limit
  1968.     subLength = -1
  1969.     if (Sort) {
  1970.     # build a subtree level to sort
  1971.     for (childNum = 1; (ind = catind childNum) in Data; childNum++)
  1972.         Arr[ind] = Data[ind]
  1973.     qsortArbIndByValue(Arr,k)
  1974.     }
  1975.     # If doing indentation equalization, there is less room for data
  1976.     if (level < equIndent)
  1977.     Length -= (equIndent - level) * levelWidth
  1978.     # A single instance of this function handles all of the immediate children
  1979.     # of a particular process.  They are iterated over here.
  1980.     for (childNum = 1; (ind = catind childNum) in Data; childNum++) {
  1981.     if (Sort)
  1982.         ind = k[childNum]
  1983.     children = (ind,1) in Data
  1984.     if (level) {    # If this is not a root node, draw indentation string
  1985.         # Determine whether this child has further siblings
  1986.         siblings = (catind (childNum+1)) in Data
  1987.         # If printing one node per line or this is not the first child,
  1988.         # indent string for this node was not drawn when its parent's node
  1989.         # data was printed, so print indent string now.
  1990.         # If child has further siblings, need a left tee indent;
  1991.         # otherwise need a lower left indent.
  1992.         if (!Width || childNum != 1) {
  1993.         printf "%s",branch Strings[siblings ? "t" : "l"]
  1994.         if (level < equIndent) {
  1995.             printf "%s",children ? Strings["bie"] : Strings["p"]
  1996.             for (i = level+1; i < equIndent; i++)
  1997.             printf "%s",Strings["p"]
  1998.         }
  1999.         printf "%s", Strings["bt"]
  2000.         }
  2001.     }
  2002.     else if (equIndent)
  2003.         printf "%s",Strings["r"]
  2004.     # Done printing whatever indentation strings need to precede this
  2005.     # child's node data; now print node data and indentation for its
  2006.     # children, if any.
  2007.     value = Data[ind]    # Get node data
  2008.     if (AddInd)
  2009.         value = ind ":" value
  2010.     if (Width && children) {
  2011.         if (subLength == -1)
  2012.         # No room left to show children; indicate that by terminating
  2013.         # with line truncation char
  2014.         printf "%.*s%s\n",Length-1,value,Strings["lt"]
  2015.         else {
  2016.         if ((len = length(value)) > Width)
  2017.             printf "%.*s%s",Width-1,value,Strings["c"] # truncate
  2018.         else
  2019.             printf "%s%s%.*s",value,Strings["smacs"], Width-len,
  2020.             Strings["p"]    # pad on right
  2021.         # If this node has children, print offset branch
  2022.         printf "%s",Strings[((ind,2) in Data) ? "tn" : "n"]
  2023.         }
  2024.     }
  2025.     else if (Length) {
  2026.         if (length(value) > Length)
  2027.         printf "%.*s%s\n",Length-1,value,Strings["lt"]
  2028.         else
  2029.         printf "%.*s\n",Length,value
  2030.     }
  2031.     else
  2032.         print value
  2033.     if (children && subLength != -1) {
  2034.         if (level)
  2035.         nbranch = branch Strings[siblings ? "v" : "b"]
  2036.         dtTraverse(Data,ind SUBSEP,Strings,level+1,nbranch,Width,subLength,
  2037.         levelWidth,AddInd,Sort,equIndent)
  2038.     }
  2039.     }
  2040. }
  2041.  
  2042. # buildTree: add nodes to a tree, find each of their children, and call
  2043. # buildTree() recursively for each child set.
  2044. # Tree[] is the tree being built, in the style described for DrawTrees().
  2045. # treeData[1..n] contains data that should be added to Tree[] (a string may
  2046. # modified by getChildren() if it is called for a node).
  2047. # Prefix is the string that the index of each element in treeData[] should be
  2048. # prefixed with when it is copied to Tree[].
  2049. # Depth is the current depth within the tree, with the top node at depth 1.
  2050. # It is used only to be passed to getChildren() in case it cares.
  2051. # childData[1..n] has two purposes.  buildTree() will only call getChildren()
  2052. # for those indexes of treeData[] that also exist in childData[].  In addition,
  2053. # additional data may be passed to getChildren() for a node by assigning a
  2054. # value to the node index in childData[].
  2055. #
  2056. # For each element in childData[], the function getChildren() is called with
  2057. # the parameters (treeData,childData[i],cTreeData,cChildData,i,Depth).
  2058. # cTreeData[] and cChildData[] are arrays which should be filled in the node
  2059. # has any children.
  2060. # The return value of getChildren() should be the number of children found.
  2061. # treeData[] is passed rather than the value of one of its elements so that
  2062. # the value of the element being processed may be modified before it is
  2063. # copied to Tree[].  If it is deleted from the array, it is skipped (not
  2064. # copied to Tree[]); in this case no children should be added.
  2065. # getChildren() must be defined elsewhere in the program.
  2066. function buildTree(Tree,treeData,childData,Prefix,Depth,
  2067. i,cTreeData,cChildData,j) {
  2068.     j = 1
  2069.     for (i = 1; i in treeData; i++) {
  2070.     split("",cTreeData)
  2071.     split("",cChildData)
  2072.     if (i in childData && \
  2073.     getChildren(treeData,childData[i],cTreeData,cChildData,i,Depth) && \
  2074.     i in treeData)
  2075.         buildTree(Tree,cTreeData,cChildData,Prefix j SUBSEP,Depth+1)
  2076.     if (i in treeData)
  2077.         Tree[Prefix j++] = treeData[i]
  2078.     }
  2079. }
  2080.  
  2081. # Breadth-first-search version of buildTree().  This is intended to flatten
  2082. # the tree representation of a possibly cyclic graph as much as possible.
  2083. # All nodes at each depth are visited before the nodes at the next depth are
  2084. # visited.
  2085. # All parameters are as for buildTree() except that the scalar Prefix is
  2086. # replaced by the array Prefixes[].  It has an element for each value in
  2087. # treeData[] (with the same index), with the value being the prefix for the
  2088. # index which that element should be stored in treeData[] with.
  2089. # getChildren() is called as by buildTree(), except that there is an additional
  2090. # argument telling getChildren() the first index in cTreeData[] and
  2091. # cChildData[] to use (instead of starting at 1).
  2092. function bfBuildTree(Tree,treeData,childData,Prefixes,Depth,
  2093. i,cTreeData,cChildData,j,childPos,cPrefixes,nChild,cIndex,l) {
  2094.     childPos = 1
  2095.     for (i = 1; i in treeData; i++) {
  2096.     nChild = (i in childData) ? \
  2097.     getChildren(treeData,childData[i],cTreeData,cChildData,i,Depth,
  2098.     childPos) : 0
  2099.     if (i in treeData) {    # if not skipping this node
  2100.         if (i == 1 || Prefixes[i] != Prefixes[i-1])
  2101.         j = 1
  2102.         cIndex = Prefixes[i] j SUBSEP
  2103.         for (l = 1; l <= nChild; l++)
  2104.         cPrefixes[childPos++] = cIndex
  2105.         Tree[Prefixes[i] j] = treeData[i]
  2106.         j++
  2107.     }
  2108.     }
  2109.     if (childPos > 1)
  2110.     bfBuildTree(Tree,cTreeData,cChildData,cPrefixes,Depth+1)
  2111. }
  2112.  
  2113. # preOrderTraversal: Do a pre-order traversal of the tree described by Tree[]
  2114. # (the parent node is visited before any children).
  2115. # Tree[] is indexed by node name; the data for each node is a comma-separated
  2116. # list of the children of that node.  Nodes that do not have children need not
  2117. # appear in Tree[].  Node names may be any string that does not include a
  2118. # comma.
  2119. # Node is the node to start at.
  2120. # If nSort is true, child node names are expected to be numbers and are
  2121. # visited in numeric order.
  2122. # Ind is uses as the top level node in building indexes for a DrawTrees() style 
  2123. # tree.  The indexes are passed to preOrderVisit().  Ind would typically be 1.
  2124. # Data is an arbitrary parameter to be passed to the function that does node
  2125. # processing.  It may be a string or array, but must be consistent within a
  2126. # program, since awk will compile it as one or the other.
  2127. # Parents should be left null on the first call.
  2128. # For each node, preOrderVisit(node-id,node,ind,data) is called.
  2129. # node-id is concatenation of all of the names of the nodes that lead from the
  2130. # initial node to the node being processed, separated by commas, e.g.:
  2131. # initialnode,internode,thisnode
  2132. # node is the node name by itself.
  2133. # ind is a DrawTrees() style node index, for use if preOrderVisit() is building
  2134. # a tree for DrawTrees().  ind starts at 1 and is incremented by the amount
  2135. # that preOrderVisit() returns when called to process a subtree.  This is done
  2136. # by having preOrderVisit() return whatever value the preOrderVisit() it calls
  2137. # returns.  preOrderVisit() would typically return 0 if the index passed to it
  2138. # was not used, and 1 if it was used.
  2139. # Data is passed from the arguments to this function.
  2140. # If preOrderVisit() returns true, its children (if any) will be visited;
  2141. # if it returns false, they will not.
  2142. # preOrderVisit() should be constructed to process the node appropriately.
  2143. function preOrderTraverse(Node,Tree,nSort,Ind,Data,Parents,
  2144. Children,numChildren,childNum,subIndex,ret) {
  2145.     if ((ret = preOrderVisit(Parents Node,Node,Ind,Data)) && Node in Tree) {
  2146.     numChildren = split(Tree[Node],Children,",")
  2147.     if (nSort) {
  2148.         for (i = 1; i <= numChildren; i++)
  2149.         Children[i] += 0    # mark as numeric, for qsort
  2150.         qsortNumIndByValue(Children,1,numChildren)
  2151.     }
  2152.     Parents = Parents Node ","
  2153.     subIndex = 1
  2154.     for (childNum = 1; childNum <= numChildren; childNum++)
  2155.         subIndex += preOrderTraverse(Children[childNum],Tree,nSort,
  2156.         Ind SUBSEP subIndex,Data,Parents)
  2157.     }
  2158.     return ret
  2159. }
  2160. ### End of DrawTrees lib
  2161.